commit fc1a92942889e72a1fcf7d817fe16c404a9e4889 Author: j Date: Fri Nov 12 16:49:35 2021 +0000 event timeline diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3be662d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +[*] +end_of_line = lf +insert_final_newline = true + +[*.py] +indent_style = space +indent_size = 4 + +[*.sass] +indent_style = space +indent_size = 2 + +[*.scss] +indent_style = space +indent_size = 2 + +[*.html] +indent_style = tab +indent_size = 4 + +[*.js] +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b24304 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +venv +__pycache__ +db.sqlite3 +www/ +*.swp +venv +*.swo +secret.txt +app/local_settings.py +geo/GeoLite2-City.mmdb diff --git a/README.md b/README.md new file mode 100644 index 0000000..ead05ba --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ + +Getting started: +``` +python3 -m venv venv +./venv/bin/pip install -r requirements.txt +./manage.py migrate +./mange.py runserver +``` diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/asgi.py b/app/asgi.py new file mode 100644 index 0000000..895c0e0 --- /dev/null +++ b/app/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for app project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +application = get_asgi_application() diff --git a/app/event/__init__.py b/app/event/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/event/admin.py b/app/event/admin.py new file mode 100644 index 0000000..fc7b40f --- /dev/null +++ b/app/event/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin + +from . import models + +@admin.decorators.register(models.Event) +class EventAdmin(admin.ModelAdmin): + list_display = ( + 'title', + 'position', + 'date', + 'type', + ) + list_editable = ['position', 'date'] + diff --git a/app/event/apps.py b/app/event/apps.py new file mode 100644 index 0000000..2227ea4 --- /dev/null +++ b/app/event/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EventConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'app.event' diff --git a/app/event/migrations/0001_initial.py b/app/event/migrations/0001_initial.py new file mode 100644 index 0000000..96477fd --- /dev/null +++ b/app/event/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.9 on 2021-11-12 14:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('modified', models.DateTimeField(auto_now=True)), + ('slug', models.SlugField(blank=True, unique=True)), + ('title', models.TextField(blank=True)), + ('body', models.TextField(blank=True, null=True)), + ('date', models.TextField(blank=True, null=True)), + ('data', models.JSONField(blank=True, default=dict)), + ], + ), + ] diff --git a/app/event/migrations/0002_event_type.py b/app/event/migrations/0002_event_type.py new file mode 100644 index 0000000..0cabd73 --- /dev/null +++ b/app/event/migrations/0002_event_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2021-11-12 14:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='type', + field=models.TextField(blank=True, default=''), + ), + ] diff --git a/app/event/migrations/0003_auto_20211112_1503.py b/app/event/migrations/0003_auto_20211112_1503.py new file mode 100644 index 0000000..1c6a652 --- /dev/null +++ b/app/event/migrations/0003_auto_20211112_1503.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.9 on 2021-11-12 15:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0002_event_type'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='position', + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name='event', + name='data', + field=models.JSONField(blank=True, default=dict, editable=False), + ), + migrations.AlterField( + model_name='event', + name='date', + field=models.CharField(blank=True, max_length=1024, null=True), + ), + migrations.AlterField( + model_name='event', + name='type', + field=models.CharField(blank=True, default='', max_length=1024), + ), + ] diff --git a/app/event/migrations/0004_alter_event_options.py b/app/event/migrations/0004_alter_event_options.py new file mode 100644 index 0000000..95d21fb --- /dev/null +++ b/app/event/migrations/0004_alter_event_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.9 on 2021-11-12 15:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('event', '0003_auto_20211112_1503'), + ] + + operations = [ + migrations.AlterModelOptions( + name='event', + options={'ordering': ('position',)}, + ), + ] diff --git a/app/event/migrations/__init__.py b/app/event/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/event/models.py b/app/event/models.py new file mode 100644 index 0000000..7196a52 --- /dev/null +++ b/app/event/models.py @@ -0,0 +1,26 @@ +from django.db import models +import ox + +class Event(models.Model): + + class Meta: + ordering = ('position', ) + + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + + slug = models.SlugField(blank=True, unique=True) + + position = models.IntegerField(default=0) + title = models.TextField(blank=True) + type = models.CharField(blank=True, default='', max_length=1024) + date = models.CharField(blank=True, null=True, max_length=1024) + body = models.TextField(blank=True, null=True) + + data = models.JSONField(default=dict, blank=True, editable=False) + + def __str__(self): + return '%s (%s)' % (ox.strip_tags(self.title), self.slug) + + def get_absolute_url(self): + return '/#' + self.slug diff --git a/app/event/tests.py b/app/event/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/app/event/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/event/views.py b/app/event/views.py new file mode 100644 index 0000000..948db4c --- /dev/null +++ b/app/event/views.py @@ -0,0 +1,13 @@ +from django.shortcuts import render + + +from .models import Event +from ..page.models import Page + + +def index(request, slug=''): + context = {} + context['events'] = Event.objects.all().order_by('position') + context['postscript'] = Page.objects.get(slug='postscript') + context['intro'] = Page.objects.get(slug='intro') + return render(request, 'index.html', context) diff --git a/app/page/__init__.py b/app/page/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/page/admin.py b/app/page/admin.py new file mode 100644 index 0000000..65d81b8 --- /dev/null +++ b/app/page/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin + +from . import models + +@admin.decorators.register(models.Page) +class PageAdmin(admin.ModelAdmin): + pass diff --git a/app/page/apps.py b/app/page/apps.py new file mode 100644 index 0000000..76696df --- /dev/null +++ b/app/page/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PageConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'app.page' diff --git a/app/page/migrations/0001_initial.py b/app/page/migrations/0001_initial.py new file mode 100644 index 0000000..88705b6 --- /dev/null +++ b/app/page/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.9 on 2021-11-12 15:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Page', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True)), + ('modified', models.DateTimeField(auto_now=True)), + ('slug', models.SlugField(blank=True, unique=True)), + ('body', models.TextField(blank=True, null=True)), + ], + ), + ] diff --git a/app/page/migrations/__init__.py b/app/page/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/page/models.py b/app/page/models.py new file mode 100644 index 0000000..1913e06 --- /dev/null +++ b/app/page/models.py @@ -0,0 +1,13 @@ +import logging + +from django.db import models + +class Page(models.Model): + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + + slug = models.SlugField(blank=True, unique=True) + body = models.TextField(blank=True, null=True) + + def __str__(self): + return self.slug diff --git a/app/page/page/__init__.py b/app/page/page/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/page/page/admin.py b/app/page/page/admin.py new file mode 100644 index 0000000..c6fe108 --- /dev/null +++ b/app/page/page/admin.py @@ -0,0 +1,2 @@ +from django.contrib import admin + diff --git a/app/page/page/apps.py b/app/page/page/apps.py new file mode 100644 index 0000000..6ab5e1e --- /dev/null +++ b/app/page/page/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PageConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'page' diff --git a/app/page/page/migrations/__init__.py b/app/page/page/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/page/page/models.py b/app/page/page/models.py new file mode 100644 index 0000000..c5ba80c --- /dev/null +++ b/app/page/page/models.py @@ -0,0 +1,5 @@ +import logging + +from django.db import models + +# Create your models here. diff --git a/app/page/page/tests.py b/app/page/page/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/app/page/page/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/page/page/views.py b/app/page/page/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/app/page/page/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/app/page/tests.py b/app/page/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/app/page/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/page/views.py b/app/page/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/app/page/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/app/settings.py b/app/settings.py new file mode 100755 index 0000000..806696a --- /dev/null +++ b/app/settings.py @@ -0,0 +1,172 @@ +""" +Django settings for app project. + +Generated by 'django-admin startproject' using Django 3.2.7. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'sass_processor', + + 'app', + 'app.user', + 'app.page', + 'app.event', + +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'app.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + 'builtins': [ + 'django.templatetags.static', + ] + }, + }, +] + +WSGI_APPLICATION = 'app.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/3.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] +AUTH_USER_MODEL = 'user.User' + + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = '/static/' +STATIC_ROOT = BASE_DIR / 'www' / 'static' +MEDIA_ROOT = BASE_DIR / 'data' / 'media' + +TITLE = 'Example Timeline' + +STATICFILES_FINDERS = [ + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'sass_processor.finders.CssFinder', +] + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + + +GEOIP_PATH = BASE_DIR / 'geo' + +URL_PREFIX = '' + +try: + from .local_settings import * +except ImportError: + pass + +# Make this unique, creates random key first at first time. +try: + SECRET_KEY +except NameError: + SECRET_FILE = BASE_DIR / 'secret.txt' + try: + SECRET_KEY = open(SECRET_FILE).read().strip() + except IOError: + try: + from django.utils.crypto import get_random_string + chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)' + SECRET_KEY = get_random_string(50, chars) + secret = open(SECRET_FILE, 'w') + secret.write(SECRET_KEY) + secret.close() + except IOError: + raise Exception('Please create a %s file with random characters to generate your secret key!' % SECRET_FILE) diff --git a/app/static/css/partials/_layout.scss b/app/static/css/partials/_layout.scss new file mode 100644 index 0000000..a8199cc --- /dev/null +++ b/app/static/css/partials/_layout.scss @@ -0,0 +1,39 @@ +.intro, +.postscript, +.event { + border-bottom: 1px solid black; + max-width: 800px; + margin: auto; +} +p { +} + +.campaigns { + color:#1abc9c; +} +.places { + color:#f39c11; +} +.actions { + color:#ff00cc; +} +.council{ + color:#3399cc; +} +.event .side-by-side { + display: flex; + +} +.media, .text { + width: 50%; + padding: 8px; +} +.media iframe { + width: 100%; + height: 100%; +} +.media img { + width: 100%; + height: 100%; + object-fit: contain; +} diff --git a/app/static/css/partials/_reset.scss b/app/static/css/partials/_reset.scss new file mode 100644 index 0000000..d9f27b5 --- /dev/null +++ b/app/static/css/partials/_reset.scss @@ -0,0 +1,48 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/app/static/css/style.scss b/app/static/css/style.scss new file mode 100755 index 0000000..74d114f --- /dev/null +++ b/app/static/css/style.scss @@ -0,0 +1,2 @@ +//@import "partials/reset"; +@import "partials/layout"; diff --git a/app/static/html/404.html b/app/static/html/404.html new file mode 100644 index 0000000..7b69a4c --- /dev/null +++ b/app/static/html/404.html @@ -0,0 +1 @@ +not found diff --git a/app/static/html/50x.html b/app/static/html/50x.html new file mode 100644 index 0000000..74bb638 --- /dev/null +++ b/app/static/html/50x.html @@ -0,0 +1 @@ +failed diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..f44de66 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,19 @@ +{% load sass_tags static %} + + + + + + {% block title %} + {% endblock title %} + + + {% block head %}{% endblock head %} + + +
+ {% block main %}{% endblock main %} +
+ {% block end %}{% endblock end %} + + diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..09e3ec5 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% block title %} +{% endblock title %} +{% block main %} +
+
+ {{ intro.body | safe }} +
+
+ {% for event in events %} +
+ {% if request.user.is_staff %} + [edit] + {% endif %} +

{{ event.title | safe }}

+
{{ event.date }}
+ {{ event.body | safe }} +
+ {% endfor %} +
+
+ {{ postscript.body | safe }} +
+
+{% endblock %} diff --git a/app/urls.py b/app/urls.py new file mode 100755 index 0000000..6d2c8d8 --- /dev/null +++ b/app/urls.py @@ -0,0 +1,27 @@ +"""app URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +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 import admin +from django.urls import path +from django.conf import settings + + +from .event import views as event_views + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', event_views.index, name='index'), + path('', event_views.index, name='index'), +] diff --git a/app/user/__init__.py b/app/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/user/admin.py b/app/user/admin.py new file mode 100644 index 0000000..dd16e1b --- /dev/null +++ b/app/user/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from . import models + + +@admin.decorators.register(models.User) +class UserAdmin(admin.ModelAdmin): + pass diff --git a/app/user/apps.py b/app/user/apps.py new file mode 100644 index 0000000..30d6aa2 --- /dev/null +++ b/app/user/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'app.user' diff --git a/app/user/migrations/0001_initial.py b/app/user/migrations/0001_initial.py new file mode 100644 index 0000000..1032658 --- /dev/null +++ b/app/user/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 3.2.7 on 2021-09-28 12:09 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.BigAutoField(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')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('modified', models.DateTimeField(auto_now=True)), + ('data', models.JSONField(default=dict)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), + ], + options={ + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, + }, + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + ] diff --git a/app/user/migrations/0002_alter_user_data.py b/app/user/migrations/0002_alter_user_data.py new file mode 100644 index 0000000..9f5aa2f --- /dev/null +++ b/app/user/migrations/0002_alter_user_data.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.7 on 2021-09-30 15:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='data', + field=models.JSONField(blank=True, default=dict, editable=False), + ), + ] diff --git a/app/user/migrations/__init__.py b/app/user/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/user/models.py b/app/user/models.py new file mode 100644 index 0000000..4657d00 --- /dev/null +++ b/app/user/models.py @@ -0,0 +1,10 @@ +import logging + +from django.db import models +from django.contrib.auth.models import AbstractUser + +logger = logging.getLogger(__name__) + +class User(AbstractUser): + modified = models.DateTimeField(auto_now=True) + data = models.JSONField(default=dict, blank=True, editable=False) diff --git a/app/user/tests.py b/app/user/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/app/user/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/user/views.py b/app/user/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/app/user/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/app/widgets.py b/app/widgets.py new file mode 100644 index 0000000..23797ce --- /dev/null +++ b/app/widgets.py @@ -0,0 +1,21 @@ +import logging +import json + +from django.forms import widgets + +logger = logging.getLogger(__name__) + +class PrettyJSONWidget(widgets.Textarea): + + def format_value(self, value): + try: + value = json.dumps(json.loads(value), indent=2, sort_keys=True, ensure_ascii=False) + # these lines will try to adjust size of TextArea to fit to content + row_lengths = [len(r) for r in value.split('\n')] + self.attrs['rows'] = min(max(len(row_lengths) + 2, 10), 30) + self.attrs['cols'] = min(max(max(row_lengths) + 2, 40), 120) + return value + except Exception as e: + logger.warning("Error while formatting JSON: {}".format(e)) + return super(PrettyJSONWidget, self).format_value(value) + diff --git a/app/wsgi.py b/app/wsgi.py new file mode 100644 index 0000000..768c6e4 --- /dev/null +++ b/app/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for app project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + +application = get_wsgi_application() diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..118d972 --- /dev/null +++ b/manage.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Django's command-line utility for administrative tasks.""" +import os +import sys + +def activate_venv(base): + if os.path.exists(base): + old_os_path = os.environ.get('PATH', '') + os.environ['PATH'] = os.path.join(base, 'bin') + os.pathsep + old_os_path + site_packages = os.path.join(base, 'lib', 'python%s' % sys.version[:3], 'site-packages') + prev_sys_path = list(sys.path) + import site + site.addsitedir(site_packages) + sys.real_prefix = sys.prefix + sys.prefix = base + # Move the added items to the front of the path: + new_sys_path = [] + for item in list(sys.path): + if item not in prev_sys_path: + new_sys_path.append(item) + sys.path.remove(item) + sys.path[:0] = new_sys_path + + +def main(): + """Run administrative tasks.""" + root_dir = os.path.normpath(os.path.abspath(os.path.dirname(__file__))) + activate_venv(os.path.normpath(os.path.join(root_dir, 'venv'))) + + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5ffb1df --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +Django +libsass +django-compressor +django-sass-processor +geoip2 + +ox +lxml +gunicorn