Tumblelog by Soup.io
Newer posts are loading.
You are at the newest post.
Click here to check if anything new just came in.

April 24 2018

Beginner's Angular 4|5 Tutorial Series for Django Developers

In the previous tutorial, we've learned how to integrate Angular 4 with Python & Django. This tutorial will be dedicated to how to get started with Angular 4|5. Throughout this beginner's series, you'll learn how you can use Angular 4|5 to build client side web applications for mobile and desktop with a Django backend.

This tutorial is a part of a tutorial series that contains the following tutorials:

Angular 5 has been released (on October 2017) so this tutorial series is updated to reflect any updates. This tutorial will provide you with all of the fundamentals to help you get started quickly developing Angular 5 applications without prior knowledge of Angular.

Angular is a powerful front-end Javascript/TypeScript framework developed by Google. It allows you to build structured client side applications and PWAs(Progressive Web Apps).

Prior knowledge of Angular is not required for this tutorial series but you'll need to have a few requirements:

  • Prior working experience or understanding of HTML & CSS.
  • Prior working experience or understanding of JavaScript.

Angular 4 Features

Angular 4 is available and comes with many improvements and new features such as:

  • Size and performance: Angular 4 applications are smaller by hundreds of kilobytes, thanks to the improvements to the View Engine which have reduced the size of generated components code by around 60% .

  • The Animations are no longer part of Angular core which means that the apps which don't use them don't need to include the extra code in their final bundles. You can use animations in your apps by using BrowserAnimationsModule which can be imported from @angular/platform-browser/animations.

  • Improved *ngIf and *ngFor: *ngIf now supports the else syntax, for example it's easy now to write templates in this way

<div *ngIf="ready ; else loading">
    <p>Hello Angular 4</p>
</div>
<ng-template #loading>Still loading</ng-template> 

If the ready variable is false Angular will show the loading template.

You can also assign and use local variables inside both *ngIf and *ngFor expressions, for example:


<div *ngFor="let el of list as users; " >
        { { el } }
</div>

  • The adoption of Angular universal as part of Angular: the Angular team has adopted Angular Universal which is a community driven project that allows developers to use server side rendering for Angular apps. It's available from @angular/platform-server.

You can find more information on:Angular 4.0.0 now available official Angular blog post and Angular 4.1.0 now available official Angular blog post

Getting Started with Angular 4 / Angular 5

If you want to get started developing Angular 4/5 web applications, you have multiple options:

  • Install Angular by hand,
  • Install and use Angular CLI,
  • Upgrade from an existing Angular 2+ project.

Before you can install Angular you need to have Node.js and NPM installed on your development machine.

So go ahead and open your terminal and type the following

node -v

If you get the version of an installed Node.js then you already have the platform installed. If the command is unknown by your terminal then you need to install Node.js.

Installing Node.js is easy and straightforward, you just need to visit their official website then grab the installer for your operating system and follow the instructions.

Now if you open your terminal under Linux/MAC or command prompt under Windows and execute

node -v 

You should get an output displaying your Node.js installed version

Updating to Angular 4 from Angular 2

If you have already an Angular 2 project and want to update it to Angular 4, you can do that by simply installing a few npm packages.

Windows

Just copy and paste the following command in your prompt


npm install @angular/common@latest @angular/compiler@latest @angular/compiler-cli@latest @angular/core@latest @angular/forms@latest @angular/http@latest @angular/platform-browser@latest @angular/platform-browser-dynamic@latest @angular/platform-server@latest @angular/router@latest @angular/animations@latest typescript@latest --save

Linux and MAC

Copy and execute this on your terminal

npm install @angular/{common,compiler,compiler-cli,core,forms,http,platform-browser,platform-browser-dynamic,platform-server,router,animations}@latest typescript@latest --save 


Installing the Angular CLI

The Angular CLI is a handy command line utility built by the Angular team to easily and quickly generate new Angular applications and serve them locally. It can also be used to generate different Angular constructs such as components, services and pipes etc.

Before you can use the Angular CLI, you need to install it via npm, so go ahead and open your terminal or your command prompt then simply enter:

npm install -g @angular/cli

To check the version of your installed Angular CLI, type:

ng -v

You can also run ng -v from inside an Angular project to get the version of Angular

Generating an Angular 4 / Angular 5 Project Using the Angular CLI

Using the Angular CLI, you can generate an Angular 4+ project with a few commands, the CLI will take care of generating the project files and install all the required dependencies.

Open your terminal or your command prompt then run:

ng new angular4-project 

After finishing the installation enter:

cd angular4-project 
ng serve 

Your project will be served locally from http://localhost:4200.

Generating an Angular 4 from GitHub Repository

You can also clone a quick-start Angular project from GitHub to generate a new Angular 4 project.

So make sure you have Git installed then run the following:

git clone https://github.com/angular/quickstart  my-proj
cd my-proj
npm install
npm start

You can find more information here.

Angular 5 Features

Angular 5, code named pentagonal-donut, was just released. It has new features and internal changes which make Angular applications faster and smaller. In this section we will go over the most important changes and instructions on how to upgrade your existing Angular 2+ project to latest version.

  • As of Angular 5.0.0, the build optimizer is enabled by default which applies a series of optimizations to builds.

  • The Angular team has also improved the compiler which can make now faster rebuilds (especially for production and AOT builds) thanks to incremental compilation.

  • The Angular team has added many features to Angular decorators.

  • Developers can now ship faster and smaller bundles by removing white spaces.

  • The Angular compiler now supports TypeScript 2.3 Transforms a new feature that enables you to hook into the standard TypeScript compilation pipeline. Make sure to use the --aot switch to enable this feature.

ng serve --aot

  • You can now get rid of white spaces in template's code by setting preserveWhitespaces to false in the component's decorator. You can also turn it on globally by setting "preserveWhitespaces":falseunderangularCompilerOptions in tsconfig.json. This will help reduce your app's final bundle size.
  • You can now use lambdas inside Angular componet's decorator.
  • New improved number, date, and currency pipes that have better standardization support across browsers without i18n polyfills.
  • The old HTTP module is now deprecated in favor of HttpClient which was introduced in Angular 4.3

Changes before Upgrading

If you have an existing Angular 2 or Angular 4 project, you need to make sure you apply some changes to your project's source code before you can upgrade to Angular 5. This is the list of changes that need to be done.

https://angular-update-guide.firebaseapp.com/

  • First of all, upgrade essential Angular packages to latest version 5.0.0.

$ npm install @angular/{animations,common,compiler,compiler-cli,core,forms,http,platform-browser,platform-browser-dynamic,platform-server,router}@5.0.0


  • Upgrade to TypeScript 2.4.2 and RxJS 5.5.2. RxJS v5.5.2 is the default reactive library for Angular 5.

  • Change <template> to <ng-template> in your project's code.

  • Upgrade HTTP API to HttpClient API since HTTP module is deprecated in Angular 5.

  • If your project is making use of the Flex Layout module, you need to upgrade to its latest release which is compatible with Angular 5 (OpaqueToken was removed in Angular 5).

Getting Started with Angular 5 from Scratch

Fortunately for you, if you already have a previous working experience with Angular 2 or Angular 4, starting a new Angular 5 project is very much the same process.

In case you don't have any previous experience with Angular framework just follow the instructions below to install Angular 5 from scratch.

Prerequisites

Before you can install Angular 5, you need to have some prerequisites.

  • You need to have Node.js installed.
  • You need to have NPM (Node Package Manager) installed.

Don't worry both requirements can be installed by going to the official website and download the installer for your operating system.

Next install the latest CLI from npm by running the following command from your terminal:

npm install @angular/cli -g


Once the Angular CLI v1.5.0 is installed on your system. You can create Angular 5 applications using the ng command.

You can check for the installed version of the Angular CLI using:

$ ng -v


You should get an output like:

Angular CLI: 1.5.0
Node: 6.11.4
OS: linux ia32
Angular: 
...


You can create your first Angular 5 project using one command:

$ ng new a-new-project --style=scss --routing


You can notice the two flags at the end, --style=scss which instructs the Angular CLI to use SCSS for styling and --routing for adding basic routing support to the new Angular project.

Once the project is scaffolded, you can navigate inside your project then serve it.

$ cd a-new-project
$ ng serve

That's it, you now have a new Angular 5 project ready for you to build your next awesome Angular application.

Just like Angular 4, you can also use the quick start project from Github to generate Angular 5 projects.

git clone https://github.com/angular/quickstart angular5project
cd angular5project 
npm install
npm start

Conclusion

Thanks to Angular CLI v1.5.0 you can get started with Angular 5 by generating a new project quickly with a variety of flags to customize and control the generation process.

Now that we have created a new project, in the next tutorial, we're going to start learning about the fundamentals of Angular 5 starting with components.

On the previous section we have seen different ways to create a new Angular 4 project or updating an existing Angular 2+ project to use Angular 4.

April 22 2018

Authenticating via JWT using Django, Axios, and Vue

Getting Django Rest Framework, JWT, Axios, and Vue.js to play nice isn't easy. Here's my quick-and-dirty cheatsheet that I wrote while glueing the pieces together.

Note: My architecture doesn't use django-webpack-loader. Instead, I'm running Django and Vue.js as two separate projects. I do this because I much prefer generating new projects with vue create over configuring webpack.

The Back-End

First, install some Django parts using the installer of your choice:


pip install Django
pip install djangorestframework
pip install django-cors-headers
pip install djangorestframework-jwt

Then, configure Django in settings.py:


INSTALLED_APPS = (
    ...
    'rest_framework',
    'rest_framework.authtoken',  
    'rest_framework.authtoken',
    'corsheaders',
)


MIDDLEWARE_CLASSES = (
  ...
  'corsheaders.middleware.CorsMiddleware',
)

CORS_ORIGIN_ALLOW_ALL = False
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
    # TODO - set this properly for production
    'http://127.0.0.1:8080',
    'http://127.0.0.1:8000',
)


REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        # By default we set everything to admin,
        #   then open endpoints on a case-by-case basis
        'rest_framework.permissions.IsAdminUser',
    ),
    'TEST_REQUEST_RENDERER_CLASSES': (
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.TemplateHTMLRenderer'
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 20,
}

from datetime import timedelta

JWT_AUTH = {
    'JWT_ALLOW_REFRESH': True,
    'JWT_EXPIRATION_DELTA': timedelta(hours=1),
    'JWT_REFRESH_EXPIRATION_DELTA': timedelta(days=7),
}

Once that's done, it's time to do modify the urls.py:


from django.conf.urls import include, url
...

urlpatterns = [
    ...
    # JWT auth
    url(r'^api/v1/auth/obtain_token/', obtain_jwt_token),
    url(r'^api/v1/auth/refresh_token/', refresh_jwt_token),    
    # The rest of the endpoints
    url(r'^api/v1/', include('project.api', namespace='apiv1')),
    ...
]

Run the tests and fix them as they fail.

The Front-End

First off, add this to your Vuex store:


import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'

Vue.use(Vuex)

// Make Axios play nice with Django CSRF
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'

export default new Vuex.Store({
  state: {
    authUser: {},
    isAuthenticated: false,
    jwt: localStorage.getItem('token'),
    endpoints: {
      // TODO: Remove hardcoding of dev endpoints
      obtainJWT: 'http://127.0.0.1:8000/api/v1/auth/obtain_token/',
      refreshJWT: 'http://127.0.0.1:8000/api/v1/auth/refresh_token/',
      baseUrl: 'http://127.0.0.1:8000/api/v1/'
    }
  },

  mutations: {
    setAuthUser(state, {
      authUser,
      isAuthenticated
    }) {
      Vue.set(state, 'authUser', authUser)
      Vue.set(state, 'isAuthenticated', isAuthenticated)
    },
    updateToken(state, newToken) {
      // TODO: For security purposes, take localStorage out of the project.
      localStorage.setItem('token', newToken);
      state.jwt = newToken;
    },
    removeToken(state) {
      // TODO: For security purposes, take localStorage out of the project.
      localStorage.removeItem('token');
      state.jwt = null;
    }
  }
})

Then, in a Vue component called something like components/Login.vue:


<template lang="html">
  <form class="login form">
    <div class="field">
      <label for="id_username">Username</label>
      <input
        v-model="username"
        type="text"
        placeholder="Username"
        autofocus="autofocus"
        maxlength="150"
        id="id_username">
    </div>
    <div class="field">
      <label for="id_password">Password</label>
      <input
        v-model="password"
        type="password"
        placeholder="Password"
        id="id_password">
    </div>
    <button
      @click.prevent="authenticate"
      class="button primary"
      type="submit">
      Log In
    </button>
  </form>
</template>

<script>
import axios from 'axios'

export default {
  data () {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    authenticate () {
      const payload = {
        username: this.username,
        password: this.password
      }
      axios.post(this.$store.state.endpoints.obtainJWT, payload)
        .then((response) => {
          this.$store.commit('updateToken', response.data.token)
          // get and set auth user
          const base = {
            baseURL: this.$store.state.endpoints.baseUrl,
            headers: {
            // Set your Authorization to 'JWT', not Bearer!!!
              Authorization: `JWT ${this.$store.state.jwt}`,
              'Content-Type': 'application/json'
            },
            xhrFields: {
                withCredentials: true
            }
          }
          // Even though the authentication returned a user object that can be
          // decoded, we fetch it again. This way we aren't super dependant on
          // JWT and can plug in something else.
          const axiosInstance = axios.create(base)
          axiosInstance({
            url: "/user/",
            method: "get",
            params: {}
          })
            .then((response) => {
              this.$store.commit("setAuthUser",
                {authUser: response.data, isAuthenticated: true}
              )
              this.$router.push({name: 'Home'})
            })

        })
        .catch((error) => {
          console.log(error);
          console.debug(error);
          console.dir(error);
        })
    }
  }
}
</script>

<style lang="css">
</style>

Summary

There you have it, my quick-and-dirty notes on getting Django REST Framework, JWT, Axios, and Vue.js to play nice together. Be aware there are a two significant problems:

  1. I'm not happy about using local storage, especially with JWT. In fact, my good friend Randall Degges has written about the problems of JWT.
  2. I don't cover logging out. ;)

Credits:

April 21 2018

April 20 2018

The final INTERNAL_IPS fix for development hosts

The final INTERNAL_IPS fix for development hosts

Django’s INTERNAL_IPS setting is an ongoing source of frustration and confusion (not only, but also) for users of django-debug-toolbar, especially when using non-local addresses. This is very useful for testing a website using mobile devices if you do not have a very fast internet connection where it does not matter whether you connect to a host through the local network or via the internet, for example using localtunnel.

For some time we had a utility function which automatically added all detected network interface IPs to INTERNAL_IPS. However, this does not work when using virtualization software such as Docker or Vagrant with port forwarding, because the VM’s (or container’s) IP isn’t what you want – you want the host IP.

Once I took a step back I saw a different, but much simpler solution. INTERNAL_IPS can be replaced with an object which simply answers True to all __contains__-type questions:

if DEBUG:
    # `debug` is only True in templates if the vistor IP is in INTERNAL_IPS.
    INTERNAL_IPS = type(str('c'), (), {'__contains__': lambda *a: True})()

April 19 2018

How to Apply Image Filters in OpenCV with Python

Add your own image filters on ...

Goodbye manage.py

Every Django project starts with a manage.py file in its root. It's a convenience script that allows you to run administrative tasks like Django's included django-admin.

In our last post, we discussed the merits of including a setup.py file in your project to make it installable as a true Python package. We'll build on that and show you how to properly setup a console script in the package.

Hello Entry Points

The setup function provided by setuptools accepts an entry_points keyword argument that is used to dynamically create scripts from a provided Python function on install. If we crack open the existing manage.py and strip it down to its essentials, we can see it isn't really doing much:

import os
import sys

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

It sets a default environment variable and then calls a function passing in the provided command line arguments.

This can easily be turned into an entry point by simply making it a function inside our project. I typically add it to myproject/__init__.py like so:

import os
import sys

def manage():
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
    from django.core.management import execute_from_command_line
    execute_from_command_line(sys.argv)

Now we adjust our setup.py to add the entry point:

setup(
    ...
    entry_points={'console_scripts': [
        'manage.py = myproject:manage',
    ]}
)

On install, a manage.py script will be created and added to the virtualenv's path.

It's also worth noting that the script's name is purely vestigal at this point. If you are targeting non-Django developers with your project, it might make more sense to rename it to something more logical.

Benefits

There are a few things I like about this approach.

  1. You can run manage.py from anywhere. No need to activate a virtualenv or be in the right directory. Simply run /path/to/virtualenv/bin/manage.py ... and it just works. If your virtualenv is activated, the path will already be set and manage.py will just work without the path.
  2. This is purely cosmetic, but I don't like the proliferation of files in the project root (, .dockerignore, Procfile, webpack.config.js, etc.). One less file makes me happy.

One potential downside to this approach is that manage.py is often a signal to developers, "this is a Django project". By removing it, you're more reliant on the docs to point developers in the right direction. If you prefer to keep your manage.py file, you can still benefit from this approach by wrapping the code inside a function and registering that as an entrypoint.

Have any other Django install tricks you like to use on your projects? I'd love to hear them. Please leave a note in the comments.

Django - Referencing the User Model

There are 3 different ways to refer to the User model in Django. This post explains the differences and which approach to use.

April 18 2018

Offline messages for Django

Offline messages for Django

django.contrib.messages is awesome. Its API surface is small, it’s fast and it does what it does really well.

However, it sometimes happens that you would want to send messages to users outside the request-response cycle, for example from a background process or even from a post_save signal handler.

Adding a full-blown notifications-and-messages-system is of course possible. Sending a one-off message to the user would be sufficient though, but there is no way to do this with Django’s messages framework.

There are a few packages around solving this particular problem. Almost all of them solve this by saving undelivered messages in the database. My solution, django-user-messages follows the same approach, but contrary to many others it does not reimplement the message storage nor replace any other functionality of django.contrib.messages. It only adds a few additional utilities for adding messages (e.g. user_messages.api.info(...) instead of messages.info(...)) and a context processor which concatenates Django’s messages with those provided by django-user-messages.

Instead of passing the request to messages.info(...) you would pass a user instance, or even only a user ID to api.info(...).

Easy enough, and works well. Despite the 0.5 version number the package has been stable and essentially unchanged since July 2017 (except for the addition of Python 2 support). So I wont say to get it while it’s hot, because it’s not – instead, it is boring, stable and reliable, the way I like my low maintenance software.

April 17 2018

3rd Party Haar Cascades in OpenCV

Building on [face recognition ...

April 16 2018

April 15 2018

April 14 2018

Demystifying encodings — part 3

Do you ever have trouble with encodings? I used to in the past, but now I rarely do, because I do a simple thing. While knowing the things I explained in part 1 and part 2 of this series is necessary, otherwise I wouldn’t be able to fix problems, such problems rarely arise, because the first thing I do when I setup a GNU/Linux machine is set the system locale to UTF-8.

The “locale” is the regional settings, among which the character encoding used. The procedure to set it up is this:

  1. Open the file /etc/locale.gen in an editor and make sure the line that begins with “en_US.UTF-8” is uncommented.
  2. Enter the command locale-gen; this will (re)generate the locales.
  3. Open the file /etc/default/locale in an editor, and make sure it contains the line LANG=en_US.UTF-8. Changes in this file require logout and login to take effect.

Let me now explain what all this is about. The locale consists of a language, a country, and a character encoding; “en_US.UTF-8” means English, United States, UTF-8. This tells programs to show messages in American English; to format items such as dates in the way it’s done in the United States; and to use encoding UTF-8.

Different users can be using different locales. If you have a desktop computer used by you and your spouse, one could be using English and the other French. Each user does this by setting the LANG environment variable to the desired locale; if not, the default system locale is used for that user. For servers this feature is less important. While your Django application may display the user interface in different languages (and format dates and numbers in different ways), this is done by Django itself using Django’s internationalization and localization machinery and has nothing to do with what we are discussing here, which affects mostly the programs you type in the command line, such as ls. Because for servers the feature of users specifying their preferred locale isn’t so important, we usually merely use the default system locale, which is specified in the file /etc/default/locale. Since you can understand English, “en_US.UTF-8” is fine. If you prefer to use another country, such as “en_UK.UTF-8”, it’s also fine, but it’s no big deal, as I will explain later on.

Although the system can support a large number of locales, many of these are turned off in order to save a little disk space. You turn them on by adding or uncommenting them in file /etc/locale.gen. When you execute the program locale-gen, it reads /etc/locale.gen and determines which locales are activated, and it compiles these locales from their source files, which are relatively small, to some binary files that are those actually used by the various programs. We say that the locales are “generated”. If you activate all locales the binary files will be a little bit over 100 M, so the saving is not that big (it was important 15 years ago); however they will take quite some time to generate. Usually we only activate a few.

To check that everything is right, do this:

  1. Enter the command locale; everything (except, possibly, LANGUAGE and LC_ALL) should have the value en_US.UTF-8.Enter the command
  2. perl -e ''; it should do nothing and give no message.

The locale command merely lists the active locale parameters. LC_CTYPE, LC_NUMERIC etc. are called “locale categories”, and usually they are all set to the same value. In some edge cases they might be set to different values; for example, on my laptop I use “en_US.UTF-8”, but especially for LC_TIME I use “en_DK.UTF-8”, which causes Thunderbird to display dates in ISO 8601. This is not our concern here and it rarely is on a server. So we don’t set any of these variables, and they all get their value from LANG, which is set by /etc/default/locale.

However, sometimes you might make an error; you might specify a locale in /etc/default/locale, but you might forget to generate it. In that case, the locale command will indicate that the locale is active, but it will not show that anything is wrong. This is the reason I run perl -e ''. Perl is a programming language, like Python. The command perl -e '', does nothing; it tells Perl to execute an empty program; same thing as python -c ''. However, if there is anything wrong with the locale, Perl throws a big warning message; so perl -e '' is my favourite way of verifying that my locale works. Try, for example, LANG=el_GR.UTF-8 perl -e '' to see the warning message. So locale shows you which is the active locale, and perl -e '', if silent, indicates that the active locale has been generated and is valid.

I told you that the country doesn’t matter much for servers. Neither does the language. What matters is the encoding. You want to be able to manipulate all characters of all languages. Even if all your customers are English speaking, there may eventually be some remark about a Chinese character in a description field. Even if you are certain there won’t, it doesn’t make any sense to constrain yourself to an encoding that can represent only a subset of characters when it’s equally easy to use UTF-8. So you need to make sure you use UTF-8.

PostgreSQL will use the default locale for its databases (unless told to do otherwise). Your best bet is to do all the above before apt install postgresql.

The programs you run at the command line will be producing output in your chosen encoding. Your terminal reads the bytes produced by these programs and must be able to decode them properly, so it must know how they are encoded. In other words, you must set your terminal to UTF-8 as well. Most terminals, including PuTTY and gnome-terminal, are by default set to UTF-8, but you can change that in their preferences.

This post is largely taken from the first chapter of my book.

Was this post useful?

The post Demystifying encodings — part 3 appeared first on Django deployment.

April 13 2018

April 12 2018

OpenCV & Python: Face Recognition and Identification

I'm really starting to enjoy w...

Save an Auto Generated PDF File to Django model

To save a auto-generated PDF f...

April 11 2018

For hire

Since a slightly-wider circle of people know this now, it’s time to just go public with the news: my last day at now-former employer was toward the end of January. At the time I told a few friends, but wasn’t in a huge rush to find something else immediately; I’d been getting ready to depart for a little while, and there were some things (a bit of travel, catching up on some open-source projects, and ...

Read full entry

April 09 2018

April 06 2018

Dynamic generate urlpatterns for django 2

Create dynamic urlpatterns for classed based views

February 17 2018

Django Generate Unique Slug

Generate unique slug refference by another field.

Older posts are this way If this message doesn't go away, click anywhere on the page to continue loading posts.
Could not load more posts
Maybe Soup is currently being updated? I'll try again automatically in a few seconds...
Just a second, loading more posts...
You've reached the end.

Don't be the product, buy the product!

Schweinderl