Commit 9fa6b7b7 authored by Bart's avatar Bart

Default setup

parent df510f4c
.vscode/
node_modules/
kees/config.py
db.sqlite3
webpack-stats.json
__pycache__
# Stage 1 - Compile needed Python dependencies
FROM alpine
RUN apk --no-cache add \
linux-headers \
build-base \
python3 \
python3-dev \
musl-dev \
pcre-dev \
jpeg-dev \
zlib-dev && \
pip3 install virtualenv && \
virtualenv /app/env
WORKDIR /app
COPY requirements.txt /app
RUN /app/env/bin/pip install -r requirements.txt
# Stage 2 - Build docker image suitable for execution and deployment
FROM alpine
RUN apk --no-cache add \
ca-certificates \
mailcap \
musl \
pcre \
zlib \
jpeg \
python3
COPY . /app
COPY --from=0 /app/env /app/env
COPY ./docker/start.sh /start.sh
ENV PATH="/app/env/bin:${PATH}"
WORKDIR /app
EXPOSE 8000
CMD ["/start.sh"]
<template>
<div>
<p>This is just a demo.</p>
</div>
</template>
<script>
</script>
<style>
</style>
\ No newline at end of file
import Vue from 'vue';
import Demo from "./components/Demo.vue";
const app = new Vue({
el: '#app',
components: {
Demo
}
});
\ No newline at end of file
from django.utils.translation import gettext, gettext_lazy as _
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib import admin
from django.forms import ModelForm
from .models import User
class UserCreationForm(ModelForm):
class Meta:
model = User
fields = ('username', 'name', )
def save(self, commit=True):
user = super().save(commit=False)
if commit:
user.save()
return user
class UserAdmin(BaseUserAdmin):
add_form = UserCreationForm
list_display = ('name', 'username', 'email', 'last_visit')
list_filter = ('is_admin', )
ordering = ('name', )
filter_horizontal = ()
readonly_fields = ('last_visit', )
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('name', 'email', 'last_visit')}),
(_('Permissions'), {'fields': ('is_active', 'is_admin')})
)
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'password', 'email', 'name'),
}),
)
actions = []
# Register your models here.
admin.site.register(User, UserAdmin)
from django.apps import AppConfig
class CoreConfig(AppConfig):
name = 'core'
from re import compile
from django.utils import timezone
from django.http import HttpResponseRedirect
from django.conf import settings
from django.urls import reverse
from .models import User
EXEMPT_URLS = [compile(reverse(settings.LOGIN_URL).lstrip('/'))]
if hasattr(settings, 'LOGIN_EXEMPT_URLS'):
EXEMPT_URLS += [compile(expr) for expr in settings.LOGIN_EXEMPT_URLS]
class LoginRequiredMiddleware(object):
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings via a list of regular expressions in LOGIN_EXEMPT_URLS (which
you can copy from your urls.py).
Requires authentication middleware and template context processors to be
loaded. You'll get an error if they aren't.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
assert hasattr(request, 'user'), "The Login Required middleware\
requires authentication middleware to be installed. Edit your\
MIDDLEWARE setting to insert\
'django.contrib.auth.middleware.AuthenticationMiddleware'. If that doesn't\
work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\
'django.core.context_processors.auth'."
if not request.user.is_authenticated:
path = request.path_info.lstrip('/')
if not any(m.match(path) for m in EXEMPT_URLS):
return HttpResponseRedirect(reverse(settings.LOGIN_URL))
return self.get_response(request)
class SetLastVisitMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated:
# Update last visit time after request finished processing.
User.objects.filter(pk=request.user.pk).update(last_visit=timezone.now())
return self.get_response(request)
# Generated by Django 2.1.1 on 2018-09-26 20:51
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('username', models.CharField(max_length=255, unique=True)),
('email', models.EmailField(max_length=255, unique=True)),
('name', models.CharField(max_length=100)),
('is_active', models.BooleanField(default=True)),
('is_admin', models.BooleanField(default=False)),
('external_id', models.CharField(blank=True, max_length=50, null=True, unique=True)),
],
options={
'abstract': False,
},
),
]
# Generated by Django 2.1.1 on 2018-09-26 22:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='last_visit',
field=models.DateTimeField(null=True),
),
]
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models
from django.core.mail import send_mail
class Manager(BaseUserManager):
def create_user(self, username, name, password=None, external_id=None, is_active=True):
if not username:
raise ValueError('Users must have a username')
user = self.model(
username=username,
name=name
)
if is_active:
user.is_active = True
if password:
user.set_password(password)
if external_id:
user.external_id = external_id
user.save(using=self._db)
return user
def create_superuser(self, username, name, password):
user = self.create_user(
username=username,
name=name,
password=password
)
user.is_admin = True
user.is_active = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
objects = Manager()
username = models.CharField(max_length=255, unique=True)
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
last_visit = models.DateTimeField(null=True)
external_id = models.CharField(max_length=50, unique=True, blank=True, null=True)
REQUIRED_FIELDS = ['name']
USERNAME_FIELD = 'username'
def __str__(self):
return self.name
@property
def is_staff(self):
return self.is_admin
@property
def is_superuser(self):
return self.is_admin
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
def get_full_name(self):
return self.name
def get_short_name(self):
return self.name
def email_user(self, subject, message, from_email=None, **kwargs):
send_mail(subject, message, from_email or settings.FROM_EMAIL, [
self.email], **kwargs)
body {
padding-top: 5rem;
}
body.login {
background-color: #f3f3f3;
}
.login-logo {
margin: 0 auto 3rem auto;
width: 50px;
height: 50px;
}
.login-container {
border: 1px solid #d8e2f0;
box-shadow: 0 0 10px 0 rgba(19, 29, 41, 0.05);
background-color: #fff;
padding: 1.5rem;
margin: 0 auto;
max-width: 25rem;
}
.navbar-dark {
background-color: #29363e;
}
.navbar-brand {
padding: 0;
}
.navbar-brand span {
vertical-align: middle;
padding: .5rem 1rem;
}
.navbar-brand img {
vertical-align: middle;
height: 40px;
width: auto;
}
{% load static %}
{% get_media_prefix as MEDIA_URL %}
<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Kees · {{ config.COMPANY_NAME }}</title>
<link rel="icon" href="{{ MEDIA_URL }}{{ config.FAVICON_IMAGE }}">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
{% include 'partials/nav.html' %}
<main role="main" class="container">
{% for message in messages %}
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{% else %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{% endif %}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
{{ message }}
</div>
{% endfor %}
{% block content %}{% endblock %}
</main>
<script src="//code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body>
</html>
{% load static %}
{% get_media_prefix as MEDIA_URL %}
{% load active %}
<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Kees · {{ config.COMPANY_NAME }}</title>
<link rel="icon" href="{{ MEDIA_URL }}{{ config.FAVICON_IMAGE }}">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy"
crossorigin="anonymous">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN"
crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body class="login">
<main role="main" class="container">
<div class="login-logo">
<a href="{% url 'home' %}"><img src="{{ MEDIA_URL }}{{ config.LOGO_IMAGE }}" border="0"></a>
</div>
<div class="login-container">
{% for message in messages %}
{% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{% else %}
<div class="alert alert-success alert-dismissible fade show" role="alert">
{% endif %}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
{{ message }}
</div>
{% endfor %}
{% block content %}{% endblock %}
</div>
</main>
<script src="//code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossorigin="anonymous"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/js/bootstrap.min.js" integrity="sha384-a5N7Y/aK3qNeh15eJKGWxsqtnX/wWdSZSKp+81YjTmS15nvnvxKHuzaWwXHDli+4"
crossorigin="anonymous"></script>
</body>
</html>
{% extends '_base.html' %}
{% block content %}
<div class="row justify-content-md-center">
<div class="col-sm-8">
Welkom bij Kees.
</div>
</div>
{% endblock %}
{% load static %}
{% load active %}
<nav class="navbar navbar-expand-md navbar-dark fixed-top">
<a class="navbar-brand" href="{% url 'home' %}">
<span>Kees</span>
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#mainNav" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse offcanvas-collapse" id="mainNav">
<ul class="navbar-nav mr-auto">
<li class="nav-item {% active 'home' %}">
<a class="nav-link" href="{% url 'home' %}">Overzicht<span class="sr-only">(current)</span></a>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="user-menu" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{ request.user }}</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="user-menu">
<a class="dropdown-item" href="{% url 'logout' %}">Uitloggen</a>
</div>
</li>
</ul>
</div>
</nav>
{% extends "_login_base.html" %}
{% load i18n %}
{% block content %}
<p>Je bent succesvol uitgelogd.</p>
<p><a href="{% url 'admin:index' %}" class="btn btn-primary">{% trans 'Log opnieuw in' %}</a></p>
{% endblock %}
{% extends '_login_base.html' %}
{% block title %}Login{% endblock %}
{% block content %}
<h2>Inloggen</h2>
<form method="post">
{% csrf_token %}
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
{{ error }}
</div>
{% endfor %}
{% endif %}
<p>Vul je inloggegevens in</p>
<div class="form-group">
<input type="text" name="username" class="form-control" id="username" placeholder="E-mail">
<input type="password" name="password" class="form-control" id="password" placeholder="Password">
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary">Inloggen</button>
</div>
</form>
{% endblock %}
import re
from django import template
from django.urls import reverse, NoReverseMatch
register = template.Library()
@register.simple_tag(takes_context=True)
def active(context, pattern_or_urlname):
try:
pattern = '^' + reverse(pattern_or_urlname)
except NoReverseMatch:
pattern = pattern_or_urlname
path = context['request'].path
if re.search(pattern, path):
return 'active'
return ''
from django.test import TestCase
# Create your tests here.
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
@login_required
def index(request):
return render(request, 'index.html')
#!/bin/ash
# Collect static files
echo "Collect static files"
python manage.py collectstatic --noinput
# Apply database migrations
echo "Apply database migrations"
python manage.py migrate
# Start server
echo "Starting server"
uwsgi --http :8000 --module kees.wsgi --static-map /static=/app/static --static-map /media=/app/media
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = ''
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = []
EMAIL_HOST = 'localhost'
EMAIL_PORT = '1025'
EMAIL_HOST_USER = None
EMAIL_HOST_PASSWORD = None
EMAIL_USE_TLS = False
EMAIL_USE_SSL = False
......@@ -15,28 +15,17 @@ import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '5--l*8#s_6ewf5qg1g5!)il_5n0dx4$!u8++z(^s-c8j36gx$j'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'core',
'webpack_loader',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'constance',
'constance.backends.database',
]
MIDDLEWARE = [
......@@ -47,8 +36,48 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'core.middleware.LoginRequiredMiddleware',
'core.middleware.SetLastVisitMiddleware',
]
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
CONSTANCE_ADDITIONAL_FIELDS = {
'image_field': ['django.forms.ImageField', {}]
}
CONSTANCE_CONFIG = {
'COMPANY_NAME': ('Example', 'Company name'),
'LOGO_IMAGE': ('default/logo.svg', 'Logo', 'image_field'),
'FAVICON_IMAGE': ('default/favicon.png', 'Favicon', 'image_field'),
}
CONSTANCE_CONFIG_FIELDSETS = {
'General Options': (
'COMPANY_NAME',
'LOGO_IMAGE',
'FAVICON_IMAGE',
),
}
AUTH_USER_MODEL = 'core.User'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console': {
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
}
}
}
ROOT_URLCONF = 'kees.urls'
TEMPLATES = [
......@@ -62,6 +91,7 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'constance.context_processors.config',
],
},
},
......@@ -103,9 +133,9 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/2.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'nl-nl'
TIME_ZONE = 'UTC'
TIME_ZONE = 'Europe/Amsterdam'
USE_I18N = True
......@@ -118,3 +148,30 @@ USE_TZ = True
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'assets'),
)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
WEBPACK_LOADER = {
'DEFAULT': {
'BUNDLE_DIR_NAME': 'bundles/',
'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
}
}
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'home'
LOGIN_EXEMPT_URLS = [
'^media/(.+)$'
]
from .config import *
\ No newline at end of file
......@@ -13,9 +13,25 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib.auth import views as auth_views
from django.urls import re_path
from django.views.static import serve
from django.conf import settings
from django.contrib import admin
from django.urls import path
import core.views
urlpatterns = [
path('admin/', admin.site.urls),
path('', core.views.index, name='home'),
path('accounts/login/', auth_views.LoginView.as_view(), name='login'),
path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
path('admin/', admin.site.urls)
]
if settings.DEBUG:
urlpatterns += [
re_path(r'^media/(?P<path>.*)$', serve, {
'document_root': settings.MEDIA_ROOT