django CMS for Editors

Intro & Documentation

Before you post questions please make sure that you’ve read through the django CMS resources first:

The django CMS Admin Interface – First Steps

  • Admin Panel: The admin panel is where all functionality is accessible, it’s your starting point. You can always go to the admin panel by appending /admin to your domain (i.e. https://yourdomain.com/admin or https://your-website-1.eu.aldryn.io/admin
  • Editing a page: You can go into Edit Mode on any page, by simply adding ?edit to any URL in the browser’s address bar. So if you are on https://yourdomain.com/some-page/ then you can append ?edit to the URL (https://yourdomain.com/some-page/?edit) and hit enter. You will then see a login screen unless you are already logged in and after that be able to double click on any part of the page to edit it directly.

Editor Best Practises

Setting up a Reverse Proxy for Divio vanity media domains

There is some official documentation available at https://docs.divio.com/en/latest/how-to/configure-media-custom-domain/ – follow those steps. Then you can change the template project as follows:

Create a default.conf file in the root of the media proxy project and copy and paste the below, replace the divio project name:


#
# Test this config like this: nginx -t -c /etc/nginx/nginx.conf
#


# Production
server {
    # divio sends all traffic to port 80 as HTTPS is terminated already at the load balancer
    listen 80;

    server_name media.yourdomain.com

    client_max_body_size 20M;

    if ($http_x_forwarded_proto != 'https') {
        return 301 https://$host$request_uri;
    }

    location / {
      # for use with ssh port forwarding on the web proxy

      proxy_pass https://your-divio-project-name-live-e351eea52c934521360-55514a4.divio-media.com;
      # for use with ssh port forwarding
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host $host;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_connect_timeout 5s;
    }

}

# Testing
# this runs on media-files-reverse-proxy-stage.eu.aldryn.io

# locally, test this with curl -H "X-Forwarded-Proto: https" http://0.0.0.0:8090/file...

server {
    # divio sends all traffic to port 80 as HTTPS is terminated already at the load balancer
    listen 80 default_server;

    # catch all including local test and dev env
    server_name _ ~^(.+)$ 0.0.0.0 127.0.0.1;

    client_max_body_size 20M;

    if ($http_x_forwarded_proto != 'https') {
        return 301 https://$host$request_uri;
    }

    location / {

      proxy_ssl_protocols TLSv1.2;
      proxy_ssl_server_name on;  # prevent SSL_do_handshake() failed
      proxy_ssl_name your-divio-project-name-test-0a3745d722ae42f1a12-07e2f6f.divio-media.com;

      proxy_pass https://your-divio-project-name-test-0a3745d722ae42f1a12-07e2f6f.divio-media.com;

      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Host your-divio-project-name-test-0a3745d722ae42f1a12-07e2f6f.divio-media.com;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_connect_timeout 5s;
    }

    # Character set
    charset utf-8;

    # Logging
    access_log /var/log/nginx/access-default-site.log;
    error_log /var/log/nginx/error-default-site.log error;

}

Add the following to your Dockerfile:

COPY default.conf /etc/nginx/conf.d/default.conf

Deploy this and check if it works. You can get the DSN by logging into your main project via SSH and then execute echo $DEFAULT_STORAGE_DSN , then take that value, update the domain value and add the new DSN to the divio env vars.

deploy a production-ready React app on divio.com (HTTP to HTTPS Redirect on nginx)

The problem: Cloud hosting services such as Divio.com terminate HTTPS already at the loadbalancer level. Your container that hosts react will never receive an incoming HTTPS connection. Still, upgrading HTTP connections to HTTPS connections falls in the responsibility of your container. How to fix that?

Special thanks to Jonathan for his advice and support.

Here is how to build and serve a React app with nginx on divio.com:

Add an nginx/default.conf to your project:

## Redirect HTTP to HTTPS

server {
    # divio sends all traffic to port 80 as HTTPS is terminated already at the load balancer
    listen 80;

    client_max_body_size 20M;

    if ($http_x_forwarded_proto != 'https') {
        return 301 https://$host$request_uri;
    }

    location / {
      alias /usr/share/nginx/html/;
      try_files $uri $uri/ /index.html =404;
    }

}

change your Dockerfile accordingly:

FROM node:12.16.1 as builder

# for caching optimisations
COPY package*.json /
RUN npm install


COPY . /app
WORKDIR /app

ENV PATH=/node_modules/.bin:$PATH

RUN npm run build


FROM nginx:latest
COPY --from=builder /app/build /usr/share/nginx/html
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/default.conf /etc/nginx/conf.d

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Update your docker-compose.yml for local support and testing (optional):

# This docker-compose.yml file is used to set up your project in the local
# development environment *only*. It is *not* used in deployment to our cloud
# servers, and has no effect whatsoever in cloud deployments.
#
# See our Developer Handbook for more information:
# http://docs.divio.com/en/latest/reference/docker-docker-compose.html
version: "2.3"

services:
  # The web container is an instance of exactly the same Docker image as your
  # Cloud application container.
  web:
    build:
      context: .
      target: builder
    # Change the port if you'd like to expose your project locally on a
    # different port, for example if you already use port 8000 for
    # something else.
    ports:
     - "8000:80"
    volumes:
      - ".:/app:rw"
    # There is currently a bug: https://github.com/facebook/create-react-app/issues/8688#issuecomment-602149917
    tty: true
    # The default command that the container starts with. If you'd like to run
    # the project locally in Live configuration, you will need to change this.
    # See https://docs.divio.com/en/latest/how-to/local-in-live-mode
    command: npm start

  nginx:
    build: .
    # Change the port if you'd like to expose your project locally on a
    # different port, for example if you already use port 8000 for
    # something else.
    ports:
     - "8000:80"
    volumes:
      - "./nginx/default.conf:/etc/nginx/conf.d/default.conf"
    command: [nginx-debug, '-g', 'daemon off;']

Working with videos in Content Management Systems (CMS)

There are a few things to know when handling videos in Content Management Systems (CMS). Are you set to become a pro CMS editor? Then please read on:

Video Hosting

You might not be aware, but CMS are not a great place to host video. CMS run on web hosting, which is in most cases not suitable for hosting of large files. Also consider that the video file (for example an .mp4 video format) that you have locally on your desktop might not be optimized for the web.

On the other hand, video platforms such as youtube or vimeo have specialized in video hosting. Videos uploaded on video platforms are auto-magically compressed and optimized for the web, for the maximum compatibility! Doesn’t that sound like the right choice for hosting videos!

Good to know: Platforms such as youtube allow you to host videos for your website without showing them anywhere else, for example your youtube channel. This special mode is called an “unlisted” video.

Now how do we get Youtube play nicely together with your CMS? You have probably already seen that the video component in your CMS allows to insert a Youtube link. Let’s go get that link on youtube!

Step 1: Create an “unlisted” video on Youtube

Go to https://studio.youtube.com/ – if you don’t have a Google Account, maybe now is a good time to set up a shared gmail address for your department or team. Make sure you select the right channel or create a new one. Since it won’t be public anyway, you dont need to upload a logo or care about any optional fields. Then at some point you can upload your video:

Here you can just drag and drop your video
Set the visibility to “unlisted”
click on the link after the video has finished processing
copy the first part of the video url from the browser address bar (without &feature=youtu.be)

Step 2: Edit the page in the CMS where you would like to add the video, add a video component

This is how it looks in django CMS:

simply paste the link from youtube into your CMS video component. In django CMS it’s called video player and the field is situated in the Embed video section.

Once you save, you can now see your video nicely embedded on your page!

Pro Tipp for Youtube

Did you notice the small video thumbnails that youtube puts at the end of your video to keep the user engaged? This is often not desirable. You can limit those previews to show only other (public) videos from your channel. In django CMS’ video component this behaviour can be configured with the Parameters field right below the video URL field. Just hit the + button and set a parameter rel to the value 0. In other systems you can try appending &rel=0 to the video URL. Try this example: https://www.youtube.com/watch?v=1nOo2lrPXSo&rel=0

Working with images in Content Management Systems

There are a few things to know when handling images in Content Management Systems (CMS). Are you set to become a pro CMS editor? Then please read on:

Image size

We get it – you would like to have high quality images / photos everywhere on your website! We do, too! However bigger is not always better. It’s also important to optimize for page speed (see here why pagespeed matters). This means that the file size of our pictures should be as small as possible, while, of course, keeping the image quality on a satisfactory level.

Here is how!

Compress your Image

Some CMS compress images automatically when you upload it – but often the built-in image compression is not what it could be. On top of that, many CMS will keep the original image anyway “for the record”, using up unnecessary disk space.

It’s therefore recommendable to first upload your images to a tool such as https://www.iloveimg.com/compress-image – Here you can safely assume that your images are compressed to the max!

Just drag and drop your image(s) here!

Resize your image

Once you compressed your images, you can make your image even smaller by clicking the resize button right where you are!

After compressing, click the resize button

These days, images are best resized to either width or height at or below 4,000px (the most recent TV screens are called Ultra HD and have a 4K image resolution (3,840 x 2,160 pixels) and the most recent Macbook Air (3rd gen) has a screen resolution of  2,560 x 1,600 pixel.

If you know that your image won’t cover the full screen, you can resize it to something even smaller, for example max 500px (either width or height).

Enter a number for either height or width that is lower than the pre-filled value and press “Resize Images”

Image naming

Now you have a perfectly optimized image! Even though some CMS allow you to change the file name later on, the original file name will still be stored and will linger around! It is therefore recommended to give the image a speaking file name before you upload it to the CMS.

Good Example ✅: woman-in-green-dress-pointing-at-screen.jpg

  • Try to avoid spaces, use hyphens (the minus character) instead
  • try to describe what’s on the picture, not what you use the picture for (bad example: ❌ product-page-first-section.jpg)
  • put your image in a folder so it’s easier for you to find it later. Example folder name: Blog Post Header Images

Now you are ready to upload your picture into the CMS! 👏 Well done! 👍

By the way: Responsive Images

Some images are treated in a “smart” way by the CMS, meaning that the CMS adapts the width of the image according to the window width, and keeps the height of the image at an acceptable value according to the device or window width. Sounds like magic? It is!

Please read on about responsive images here: https://blog.typodrive.com/2019/03/09/responsive-images-and-safe-areas/

Wichtige Punkte eines Website Relaunch Briefings

Welche Punkte müssen Teil eines Website Relaunch Briefings sein?

Konzeption, UX & Design

  • Branding, CI / CD: Branding Book, Style Guide, Webfonts, Ikonographie, etc. als Input für Web Design
  • Fotografie: authentisches Fotomaterial, z.B. Portraits, Team-Bilder, etc.
  • Illustrationen: optional, ggf in Budget einplanen
  • Informations-Architektur (IA) & Inhalte: Welche Seiten, welche Hierarchien? Testimonials, Services, Produkte, Projekte, Team, Kontakt, Jobs, Blog, Landing Pages, etc.
  • Web Design: Gestaltung von einzelnen Schlüssel-Seiten durch einen professionellen Web Designer in hoher Detailtreue in XD, Sketch oder figma, als pixel-genaue Vorlage für die Umsetzung.

Inhalte

  • Content Migration: Content Management Ressourcen bereitstellen, bzw. im Budget einplanen.
  • Copywriting: Ressourcen bereitstellen für das Copywriting falls nötig, bzw. im Budget einplanen.
  • Übersetzungen & Mehrsprachigkeit: Welche Sprachen sind relevant? Für Übersetzungen ggf Ressourcen bereitstellen oder in Budget einplanen

Online Marketing

  • Suchmaschinen-Sichtbarkeit: technisches SEO, Redirects setzen, Search Console und langfristige SEO-Strategie
  • Conversion Tracking & Analytics: Google Analytics migrieren, Google Tag Manager aufsetzen, Conversion Ziele (z.B. Whitepaper Download) festlegen und Tracking aufsetzen.
  • UX & Conversion Rate Optimierung: Geeignete Call-to-Actions setzen, Optimierung der User Experience (UX)
  • Performance: Page Speed Optimierung, Server Caching, Browser Caching Settings optimieren
  • Content Strategy: Relevante Themenwelten bestimmen
  • CRM Integration: Hubspot Formulare & Tracking aufsetzen.

Technologie & Operations

  • Content Management System & Technologie Stack: Gibt es CMS- oder sogar Technologie-Präferenzen (z.B. python / django oder PHP)? Sind die Inhalts-Editoren bereits mit einem System vertraut? Sind Applikations-Logiken (z.B. ein Kundenportal) oder Integrationen mit anderen Systemen (z.B. SAP, ein DAM- oder PIM-System) geplant?
  • Device & browser Kompatibilität: Kompatibilitäts-Matrix festlegen gemäss Google Analytics Auswertung: Welche Browser & Geräte sind besonders relevant? IE11 Kompatibilität noch notwendig? Ggf im Budget entsprechend einplanen.
  • Hosting, Maintenance & Security: Sicherstellen performantes Hosting, Service Level Agreement inkl Backup und Maintenance Konditionen.

Rechtliche Aspekte

Privacy / GDPR: Muss den Usern die Möglichkeit zur vollen Datenschutz-Verwaltung gegeben werden gemäss EU-Richtlinie? Oder reicht eine abgespeckte Schweizer Lösung, z.B. Marketing cookies / scripts nur aktiv nach Opt-in des Users und Google Analytics standardmässig aktiv mit Möglichkeit zum Opt-out.

floating labels for bootstrap 4 forms

based on https://github.com/tonystar/bootstrap-float-label/blob/master/bootstrap-float-label.css

Bootstrap 4 form row:

<div class="row">
    <div class="col">
        <div class="form-group">
            <label class="has-float-label">
                <input class="form-control" name="email" type="text" placeholder="E-Mail" class="is-invalid">
                <span>E-Mail</span>
            </label>
            <p class="invalid-feedback d-block">
                This is a validation error message that needs to be inserted dynamically
            </p>
        </div>
    </div>
</div>

SCSS mixin:

@import "~bootstrap/scss/functions/";
@import "~bootstrap/scss/mixins/";
@import "~bootstrap/scss/variables/";

@mixin has-float-label {
// taken from https://github.com/tonystar/bootstrap-float-label/blob/master/bootstrap-float-label.css
display: block;
position: relative;

label, & > span {
background: white;
position: absolute;
cursor: text;
font-size: 75%;
opacity: 1;
-webkit-transition: all .2s;
transition: all .2s;
top: -.5em;
left: 0.75rem;
z-index: 3;
line-height: 1;
padding: 0 2px;
}

.form-control {

&::placeholder {
opacity: 0;
transition: all .2s;
}

&:placeholder-shown:not(:focus) + * {
font-size: 100%;
color: $input-placeholder-color;
transform: translateY(-50%);
top: 50%;
}
}

}

Payment Service Providers (PSP) that work in Switzerland

The following is a lost of payment service providers (PSP) that work in Switzerland. This is work in progress and content will change frequently.

Datatrans (datatrans.ch) – it’s an independent Payment Gateway Provider based in Switzerland. Datatrans is a bit special in that it doesnt offer acquiring. This means that you still have to set up a business relationship with a third-party acquirer such as PostFinance or a Credit Card Acquirer Bank. Datatrans then integrates all of them. Datatrans is a powerful online payment solution, but expect some additional efforts to set up a separate business relationship with an acquirer.

Beyond the standard credit cards and wallets (such as Apple Pay), it also supports PostFinance, PostFinance Card and Twint, a huge advantage in the Swiss market. It features an intuitive user interface for payment, there is publicly accessible demo available in the documentation (Lightbox Mode). It features a good User Experience via an easy to integrate frontend interface. It is developer friendly, featuring a clean programmable interface (API) and a good documentation and technical support. The state of the admin interface is unknown at the time of writing, will update soon.

Stripe (stripe.com) – Stripe is an international acquirer that supports the major credit cards. It has good support for subscription payments. It features an intuitive user interface for payment, there is publicly accessible demo available in the documentation. It is developer friendly, featuring a clean programmable interface (API) and a good documentation. The admin interface is extensive and offers a lot of control like reimbursements.

Paypal (paypal.com) – PayPal is an international online bank that supports the major credit cards. It features a good user interface and has a publicly accessible demo online here. It’s recommended to use the smart payment button and not the standard payment button as the standard one provides a sub-standard user experience. Other than that, paypal is developer friendly, featuring a clean programmable interface (API) and a good documentation. The admin interface is extensive and offers a lot of control like reimbursements.

BrainTree – braintreepayments.com – BrainTree is a subsidiary of PayPal and is positioned in the area of customized, integrated payment solutions (similar to Stripe or checkout). Its technical interface is more developer-friendly than PayPal’s and the focus of the solution is not PayPal as a brand but providing a good checkout and payment experience for the user. Check out their support here.

Checkout (checkout.com) – checkout.com is a UK-based international acquirer that supports the major credit cards. Last time I checked (2020) they allowed only merchants with an existing history and >50k in online payments per month. Also the service didn’t offer a lot of payment options for Switzerland, but this might rapidly evolve. It also supports Apple Pay in Switzerland. It features an intuitive user interface for payment, there is publicly accessible demo available in the documentation (checkout.js). It is developer friendly, featuring a clean programmable interface (API) and a good documentation. The admin interface is simple but clean and easy to use.

SIX Payment Services (six-payment-services.com) – Formerly Saferpay, SIX Payment Services is the payment solution by the Swiss payment provider SIX. It is owned by Swiss banks. SIX also offers offline payment services via phone. SIX supports all major credit cards and wallets such as Apple Pay. The user interface for payment requires a custom setup by the developer, there is no publicly accessible demo available. It is not very developer friendly. Last time I checked (2019) the admin interface was outdated and didn’t offer a good User Experience.

PostFinance Checkout (https://www.postfinance.ch/en/business/products/accounts-receivable-solutions/postfinance-checkout.html) – PostFinance Checkout supports the major credit cards plus TWINT and PostFinance Card, which is a major advantage in the Swiss market. The user interface for payment requires a custom setup by the developer, there is no publicly accessible demo available. While it offers out-of-the-box integration with some eCommerce Systems like WooCommerce (for WordPress) via plugins it is not very developer friendly and has only basic documentation.

PayRexx
https://www.payrexx.com/ – A Swiss up and coming Payment Service Provider which includes acquiring via PayRexx Direct. It supports all Swiss means of payments. It has a great admin interface and allows to integrate with the different third party payment means (like PostFinance and Twint) in a simple way. PayRexx has different ways of frontend integration, they show an easy one here: https://developers.payrexx.com/docs/showcases but at this point it doesn’t appear perfect yet (i.e. PostFinance Checkout on the web is not responsive)

always freeze requirements with pip-compile to avoid unpleasant surprises

Have you ever been in a situation, where you wanted to setup a project you haven’t been working on for a while and it failed with a cryptic error for no apparent reason?

Chances are that you didn’t freeze your requirements and one of the new packages you just installed got upgraded and the new code is incompatible with your codebase and this causes a random error!

This could be avoided by freezing requirements.txt after the initial development phase of a project. This solution however prevents an efficient upgrade process later on. What would an efficient dependency upgrade process look like?

  • Unfreeze all dependencies
  • run pip install –upgrade on all dependencies
  • test the project, if there are problems, find and downgrade the problematic package or upgrade the project’s code. If everything is ok, go to next step
  • freeze all dependencies again, check changes into source control and deploy.

Enters pip-tools with pip-compile!

pip-compile does exactly that. In order to be able to easily upgrade all packages, pip-compile works with two separate files.

  1. requirements.in It contains only the packages added by the developer. Typically, this is also the place where the developer adds comments next to a package, explaining why it is needed and describing any quirks or special information. The developer does NOT add any version numbers here, unless it’s required to make the project run (example: package-name>3.5 # doesnt work with a lower version)
  2. requirements.txt This file contains the compiled output from pip-compile including any dependent packages. No manual edits should be done here, they would be overwritten upon subsequent compilation. All packages are frozen in this file (= have a version number assigned). The project uses this file to install dependencies. It’s important that this file is included in source control (git).

Pip Compile Workflow

  • the developer manages packages in requirements.in – generally **without** versions
  • then create a new requirements.txt with docker-compose run --rm web pip-compile requirements.in > requirements.txt
  • docker-compose exec web pip install -r requirements.txt or just simply docker-compose build web
  • Testing, QA & fixes, checking changes into source control, finally deployment

django doesnt work out of the box with multiple gunicorn/uwsgi workers 🤯

This is really incredible but django has an extremely unfortunate default CACHE setting that is not ok for production environments, it defaults to a local memory cache that is not shared between different gunicorn or uwsgi workers. As a result, each worker can have a different state, even database values might differ in a django form!

Read here https://stackoverflow.com/questions/25052248/django-default-cache and here https://stackoverflow.com/questions/6422440/django1-3-multiple-gunicorn-workers-caching-problems

Solution: Set up memcached (apt get install memcached also you need to enable sockets if you want to use unix sockets, otherwise change the below config to use tcp instead) and set up django to use that cache backend in production environments:

CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': 'unix:/var/run/memcached/memcached.sock',
}
}