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/

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%;
}
}

}

freeze requirements with pip-compile as a best practise

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).

⚠️ Warning! Pypi packages can have specific versions marked as prereleases. Those are ignored by pip-compile! If you want to use a prerelease the corresponding version has to be updated manually in requirements.txt after compilation.

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

Pip Compile Setup

  1. Add pip-tools to requirements.txt
  2. Copy requirements.txt to requirements.in
  3. Run pip-compile requirements.in >> requirements.txt
  4. Rebuild the project to ensure everything works
  5. You can now remove all or some of the version pinning from your requirements.in

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',
}
}

django CMS PageField

In the models.py (it’s an extended ForeignKey field:

from cms.models.fields import PageField


faq_page_link = PageField()

How to use it in a template?

<a href="{{ instance.get_absolute_url }}">Demo Link to the FAQ Page</a>

Todo: What form widgets are available and how can they be configured on a PageField model field?

5 things you wish you’d known before founding and scaling a business in Switzerland

Nobody told you in business school, I bet.

1. Who needs a GmbH or AG?

You can start your business as a Einfache Gesellschaft. No need to spend money for a GmbH or AG. Einfache Gesellschaft allows you to use any business name you like, open a Swiss bank account in the company name (just sign a simple shareholder agreement Gesellschafter-Vertrag) and use the company name for your postal address.

If the financial risk of your business is going beyond 20k CHF you should consider founding a GmbH as the Einfache Gesellschaft doesn’t protect you as an individual from claims against the company.

2. Contracting individuals in Switzerland

Switzerland is a liberal country and contracting somebody for a job or a project is a breeze? I am sure everybody would agree, except the SVA. When it comes to social security, self-employed must prove their independence because SVA wants to protect regular employees from being forced into self-employment by their employer to save social security contributions. Before you sign an agreement with a self-employed individual, you should ask him/her for written confirmation letter from the SVA that proves his/her self-employed status.

Here is what can happen if you don’t: If you contract someone who is not recognized as self-employed and the SVA notices – and they eventually will because SVA have access to tax statements – your company will be subject to fines and back payment. The biggest cost (and pain) for your company however will be the administrative effort involved in figuring out all those contracting engagements years back.

Note that this only applies to contractors based in Switzerland because only those are subject to social security contributions to SVA.

3. VAT – register early

The ESTV (Swiss federal tax office) requires you to sign up for paying VAT for the year in which you surpass CHF 100k in revenue. Beware, don’t just wait for this to happen in the middle of your business year – you haven’t charged VAT on your invoices from earlier in that year. If later in the year you find out that your business will surpass 100k CHF in revenue, you will be forced to retroactively charge your clients the VAT. Decide on the first day of the year whether your company is likely to do more than 100k of revenue in the next 12 months and if yes charge VAT on your invoices from the very first day on.

4. Bezugssteuer

You might rejoice when you hear that your business doesn’t pay VAT on anything purchased or contracted from outside Switzerland. But nobody has told you about the Bezugssteuer. Make sure you pay Bezugssteuer whenever there is no VAT on the invoice you receive from a company outside of Switzerland, The Bezugssteuer is due even on that Cloud subscription of yours.

5. Dividends & Verrechnungssteuer

Congratulations, your company is doing good and you would like to take out some money of the company. Slow down – ESTV will only accept payments from the company to individuals as dividends if 1) your company declares earnings in its financial statement at the end of the business year 2) some of these earnings are declared to be paid out on a specific date as dividends in the protocol of the shareholder meeting.

We’re not done yet. Dividends are subject to Verrechnungssteuer. Your company is required to pay 35% of the dividend directly to ESTV. Yes that’s right, your shareholders only receive 65% of the dividends. They have to reclaim the rest via their personal tax declaration in the following year. Therefore it is good practise to define the 31.12. of the year as dividend payout date, as the shareholders then can reclaim it in their tax declaration only shortly after. It’s pretty crazy but even so some time will pass until all the money is where it belongs: year 1: financial statement of the company, year 2: dividend payment, year 3: personal tax declaration of the shareholder, year 4: ESTV pays back the Verrechnungssteuer. Four more years! 🤯

What if you (as a shareholding individual) need the money right away? If your company makes a payment to you the ESTV will classify it as salary and you will pay full SVA social contributions and income taxes on it (after the earnings of your company already got taxed). That sure hurts. It’s therefore better to receive this money as a Gesellschafter Kontokorrent loan. The money technically still belongs to the company (and you have to pay due interest for the loan to your company) but you don’t have to declare it as income in your personal tax statement and can offset the loan later against the dividend payment.

There is no incentive anymore to keep the shareholder’s loans up for longer than needed, so in most cases it’s best to settle them as soon as possible. For dividends, Federal income tax is discounted by 40% while the Staats- und Gemeindesteuer in the Canton of Zurich is discounted by 50% in order to reduce double taxation of company earnings.

Disclaimer: This is not legal advice which means you are discouraged to base your decisions on this article. Consult a lawyer.

A Google Analytics UTM Tag Guide for Marketeers

tl;dr

  • Stick with the values recommended by Google Analytics for source and medium UTM tags
  • Keep the campaign UTM tag in the same format across all different advertising networks / channels. For the same campaign, use the same name across different network / channels.
  • Assign ownership for UTM tagging to one single person to protect your Google Analytics data quality
  • Don’t use UTM tags on internal links as they overwrite the origin of your user sessions and thus destroy your acquisition data.
  • No room for mistakes as Google Analytics acquisition data is immutable. Your UTM parameter tracking will stay with you for live.

Why marketeers should read this

With great power comes great responsibility. You might not be aware of the fact that anyone with the power to create a link to your page (or even just browse it) has the power to impact the source / medium (Aquisition > Channel Attribution) data of your Google Analytics reports by adding UTM parameters to the end of the URL.

Marketeers use UTM parameters to attribute the source and type of user sessions on their websites. For example if a marketeer purchases some advertising on a news website, the marketeer will send not only the URL to his/her own website landing page (to which the ads should link) but he/she will prep the URL with Google Analytics UTM parameters. Like this, the marketeer will know how many users came from that news website.

Marketeers in charge of bigger websites will have many different active traffic sources at any given time. Google ads, bing ads, facebook organic content, facebook paid ads, email marketing, … and many more. For all these online marketing activity the marketeer will want to know how many user sessions they deliver for any given period of time, so he/she can determine the return on advertising investment on any single traffic source.

How you should track user sessions from Facebook in Google Analytics

Let’s take Facebook for example. For many professional marketeers, Facebook is more than just a traffic source, it’s at least three: 1) Users clicking on links in third-party posts on Facebook. These we can’t control and they will just show up as social traffic from Facebook. 2) Users that click on links on our own business posts on Facebook. Since we can control the content of such posts, we want to use UTM tags for those links 3) Users that click on our paid ads on Facebook. These we want to distinguish from the previous two traffic sources by using UTM tags.

Here are the most important utm parameters that Google Analytis recognizes.

utm_source: In Google Analytics, the source field of a user session is one of the most important pieces of information. Adding utm_source to a link will override the source that is determined by Google Analytics automagically. For example, a user that comes to your website from Facebook.com will create a user session in Google Analytics with the source facebook.com As this value is used in other parts of Google Analytics we recommend to not alter this behaviour and only use the real domain from where traffic is coming from. So, for Facebook posts or paid ads, you would always use a link like https://your-website.com/landing-page/?utm_source=facebook.com&utm_...

utm_medium: In Google Analytics, the medium field of a user session is the most important piece of information. Adding utm_medium to a link will override the medium that is determined by Google Analytics. Google Analytics automagically recognizes traffic mediums such as: organic, none (for direct traffic), referral, cpc, social. Medium is heavily relied on by Google Analytics to create the default channel grouping. Read more about recognized values here: https://support.google.com/analytics/answer/3297892 – We recommend to stick to these conventions under any circumstance.

utm_campaign: In Google Analytics, the campaign field of a user session is heavily relied upon by the Campaign tab which includes Google Ads. Google Ads Auto-tagging feature will use the campaign names in Google Ads to populate the campaign field in Google Analytics automagically. Beware: Google Analytics will update the campaign field even retroactively if the Google Ads campaign names are changed. We therefore recommend to create guidelines for campaign naming across all paid media networks, including Google Ads, Facebook Ads, and that news website you have booked ads with, too. This essentially means, that you define a global name for your campaigns, and that same campaign name is then used for all media, manually by setting the utm_campaign parameter or automatically via Google Ads campaign names. A good naming convention for campaigns is as follows:

DE_CH_Autumn_Campaign_2020

or a bit more machine-readable (this is useful for bigger marketing teams and marketing activities with dozens of different campaigns for filtering and automation):

[l:DE][c:CH][n:Autumn_Campaign_2020]

utm_content: In Google Analytics, the content field is reserved for further information about the ad or text around the link that was clicked upon. This field is normally not set by Google Analytics, so it’s left for you to fill with information for your ad campaigns.

Are there UTM parameters in internal links on your website?

Sometimes a marketeer would like to know whether users have clicked on a specific button, teaser or other element on their journey to a key page. It is best practise to add a query parameter such as https://your-website.com/?journey=homepage-teasers to such elements. These query parameters are then registered by Google Analytics as part of the page path of the pages visited by users. A marketeer can then filter user sessions by such a query parameter and determine what share of users have reached a key page via such elements.

Mistakenly, sometimes, UTM tags are used for such objectives. This is wrong and UTM parameters should be removed urgently from internal links when found.

Why is this so bad? The scope of UTM tagging is to determine where user sessions on your website originate. UTM parameters on internal links will overwrite this information and it is forever lost. Campaign tracking will be wrong, as some of the user sessions that should belong to a campaign of yours will loose that attribution as the UTM source / medium tags will take precedence when the user clicks on an internal link using UTM tagging on your website.

Oops I did it all wrong. Can it be fixed?

Bummer.

Most Google Analytics data, including source, medium, campaign and content fields are immutable – this data cannot be deleted or changed in Google Analytics – for the rest of your life.

The only thing you can do is correct the wrong UTM parameters as quickly as possible to at least have correct acquisition data in Google Analytics in the future.

Conclusion

It’s best not to sway too much from the default Google Analytics way of classifying incoming user sessions with the source and medium dimensions.

When more than one person is involved in campaigning and online marketing activities, designate one person to have ownership of the UTM parameter setting process. This person should provide UTM parameters for any campaigning activities. Such central management of UTM tags make sure that Google Analytics doesn’t stop making sense without anyone noticing.

Check many different URLs in jenkins with a simple bash script for uptime monitoring

This is a simple script to check whether URLs are reachable over HTTP(S). This comes in handy for example when a project has many different (secondary) domains that redirect to the main domain.

#!/bin/bash


urls=(
    "http://domain1.com",
    "https://domain1.com",
    
    "http://domain2.com",
    "https://domain2.com",
    
    "...",
) 

# remove commas
for i in "${!urls[@]}"; do     
   urls[$i]=${urls[$i]//,}
done

#for i in "${!urls[@]}"; do     
#    echo "$i"
#    echo "${urls[$i]}"
#done
#exit 0


for i in "${!urls[@]}"; do
    echo "Checking status of ${urls[$i]}"
    code=`curl -sL --connect-timeout 20 --max-time 30 -w "%{http_code}\\n" "${urls[$i]}" -o /dev/null`

    echo "Found code $code for '${urls[$i]}'"

    if [ "$code" = "200" ]; then
        echo "Website '${urls[$i]}' is online."
        online=true
        sleep 3
    else
        echo "Website '${urls[$i]}' seems to be offline. Waiting $timeout seconds."
        echo "Monitor finished with failures, at least one website appears to be unreachable."
        exit 1
    fi
done

echo "Monitor finished, all good."
exit 0