diff --git a/.dockerignore b/.dockerignore index a144add..c52a67f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,8 @@ settings.pyc settings_local.py *.ged -tmp/ -files/ -data/ +.env +.tmp/ +.files/ +.data/ +.tmp/ diff --git a/.gitignore b/.gitignore index eb8d2c2..e18b7e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ .DS_Store *.py[co] *.zip +.env +.tmp/ +.data/ +.files/ # Packages *.egg diff --git a/Dockerfile b/Dockerfile index d7e33e7..1e85bd4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,20 @@ -FROM python:2.7-alpine3.7 +FROM python:3.7-alpine3.7 WORKDIR /app/ -COPY ./reqs.frozen.pip /app/ +COPY ./reqs.pip /app/ ENV LIBRARY_PATH=/lib:/usr/lib -RUN apk --update add jpeg-dev zlib-dev build-base mariadb-dev && \ - pip install -r reqs.frozen.pip && \ - apk add mariadb-client-libs && \ - apk del build-base mariadb-dev +RUN apk --update add jpeg-dev zlib-dev build-base && \ + pip install -r reqs.pip && \ + apk del build-base + +# Create a non-root user +RUN addgroup -S appgroup && adduser -S app -G appgroup COPY ./ /app/ -RUN mkdir -p /static && python manage.py collectstatic -c --noinput +RUN mkdir -p /static && \ + chown app /static /app && \ + python manage.py collectstatic -c --noinput + +USER app + +CMD uvicorn --host=0.0.0.0 asgi:application diff --git a/asgi.py b/asgi.py new file mode 100644 index 0000000..3c2ab47 --- /dev/null +++ b/asgi.py @@ -0,0 +1,8 @@ +import os +from django.core.asgi import get_asgi_application +from django_simple_task import django_simple_task_middlware + +os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' + +app = get_asgi_application() +application = django_simple_task_middlware(app) diff --git a/docker-compose.yml b/docker-compose.yml index 8e1fc8d..c2def98 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,63 +1,24 @@ version: '2.2' services: - # Dev services - avahi: - container_name: 'avahi' - image: 'enernoclabs/avahi:latest' - network_mode: 'host' - logging: - driver: 'none' - db: - container_name: 'db' - image: 'mysql:5.7' - ports: - - '3306:3306' - environment: - MYSQL_ROOT_PASSWORD: 'docker' - MYSQL_DATABASE: 'gedgo' - MYSQL_USER: 'gedgo' - MYSQL_PASSWORD: 'gedgo' - volumes: - - './tmp:/gedgo_tmp' - logging: - driver: 'none' - redis: - container_name: 'redis' - image: 'redis' - ports: - - '6379' - logging: - driver: 'none' - # Application app: build: '.' image: 'gedgo_app' + env_file: '.env' container_name: 'gedgo_app' - command: ['python', 'manage.py', 'runserver', '0.0.0.0:8000'] + # command: ['python', 'manage.py', 'runserver', '0.0.0.0:8000'] + command: ['uvicorn', '--host=0.0.0.0', '--reload', 'asgi:application'] + ports: + - '8000:8000' volumes: - './:/app' - links: - - 'db' - - 'redis' + - '.data/:/data' web: image: 'nginx:alpine' command: 'nginx -g "daemon off;"' volumes: - './gedgo-web.conf:/etc/nginx/conf.d/default.conf:ro' - - './files:/src/files:ro' + - './.files:/src/files:ro' ports: - '80:80' links: - 'app' - worker: - container_name: 'gedgo_worker' - image: 'gedgo_app' - command: ['celery', '-A', 'gedgo.tasks', 'worker', '--loglevel=debug'] - volumes: - - './:/app' - links: - - 'app' - - 'db' - - 'redis' - depends_on: - - 'app' diff --git a/gedgo/__init__.py b/gedgo/__init__.py index 9e11c72..e69de29 100644 --- a/gedgo/__init__.py +++ b/gedgo/__init__.py @@ -1,10 +0,0 @@ -from django.conf import settings -import redis as Redis - -redis = None -if hasattr(settings, 'GEDGO_REDIS_SERVER'): - try: - redis = Redis.StrictRedis(host=settings.GEDGO_REDIS_SERVER) - redis.ping() - except Exception as e: - print e diff --git a/gedgo/gedcom_parser.py b/gedgo/gedcom_parser.py index 4d051ea..4bf2f82 100644 --- a/gedgo/gedcom_parser.py +++ b/gedgo/gedcom_parser.py @@ -17,7 +17,7 @@ class GedcomParser(object): ) def __init__(self, file_name_or_stream): - if isinstance(file_name_or_stream, basestring): + if isinstance(file_name_or_stream, str): self.file = open(file_name_or_stream, 'rU') else: self.file = file_name_or_stream @@ -70,9 +70,7 @@ def __parse_element(self, line): break # Keep the entry trimmed down - for key in entry.keys(): - if not entry[key]: - del entry[key] + entry = dict((key, entry[key]) for key in entry.keys() if entry[key]) return tag, entry diff --git a/gedgo/gedcom_update.py b/gedgo/gedcom_update.py index a140e6a..4712bbc 100644 --- a/gedgo/gedcom_update.py +++ b/gedgo/gedcom_update.py @@ -1,5 +1,5 @@ -from gedcom_parser import GedcomParser -from models import Gedcom, Person, Family, Note, Document, Event +from gedgo.gedcom_parser import GedcomParser +from gedgo.models import Gedcom, Person, Family, Note, Document, Event from django.core.files.storage import default_storage from django.db import transaction @@ -8,15 +8,19 @@ from datetime import datetime from re import findall from os import path +import sys from gedgo.storages import gedcom_storage, resize_thumb +# For logging +sys.stdout = sys.stderr + @transaction.atomic def update(g, file_name, verbose=True): # Prevent circular dependencies if verbose: - print 'Parsing content' + print('Parsing content') parsed = GedcomParser(file_name) if g is None: @@ -24,11 +28,11 @@ def update(g, file_name, verbose=True): title=__child_value_by_tags(parsed.header, 'TITL', default=''), last_updated=datetime(1920, 1, 1) # TODO: Fix. ) - if verbose: - print 'Gedcom id=%s' % g.id if verbose: - print 'Importing entries to models' + print('Gedcom id=%s' % g.id) + print('Importing entries to models') + person_counter = family_counter = note_counter = 0 entries = parsed.entries.values() for index, entry in enumerate(entries): @@ -45,26 +49,29 @@ def update(g, file_name, verbose=True): note_counter += 1 if verbose and (index + 1) % 100 == 0: - print ' ... %d / %d' % (index + 1, len(entries)) + print(' ... %d / %d' % (index + 1, len(entries))) if verbose: - print 'Found %d people, %d families, %d notes, and %d documents' % ( + print('Found %d people, %d families, %d notes, and %d documents' % ( person_counter, family_counter, note_counter, - Document.objects.count()) + Document.objects.count())) if verbose: - print 'Creating ForeignKey links' + print('Creating ForeignKey links') __process_all_relations(g, parsed, verbose) g.last_updated = timezone.now() g.save() + if verbose: + print('Done updating gedcom') + # --- Second Level script functions def __process_all_relations(gedcom, parsed, verbose=True): if verbose: - print ' Starting Person objects.' + print(' Starting Person objects.') # Process Person objects for index, person in enumerate(gedcom.person_set.iterator()): @@ -75,7 +82,7 @@ def __process_all_relations(gedcom, parsed, verbose=True): else: person.delete() if verbose: - print ' Finished Person objects, starting Family objects.' + print(' Finished Person objects, starting Family objects.') # Process Family objects for family in gedcom.family_set.iterator(): @@ -86,7 +93,7 @@ def __process_all_relations(gedcom, parsed, verbose=True): else: family.delete() if verbose: - print ' Finished Family objects.' + print(' Finished Family objects.') def __process_person_relations(gedcom, person, entry): @@ -94,7 +101,7 @@ def __process_person_relations(gedcom, person, entry): notes = gedcom.note_set # "FAMS" - person.spousal_families = [] + person.spousal_families.clear() person.spousal_families.add( *__objects_from_entry_tag(families, entry, 'FAMS') ) @@ -106,7 +113,7 @@ def __process_person_relations(gedcom, person, entry): person.child_family = child_family[0] # "NOTE" - person.notes = [] + person.notes.clear() person.notes.add(*__objects_from_entry_tag(notes, entry, 'NOTE')) person.save() @@ -117,19 +124,19 @@ def __process_family_relations(gedcom, family, entry): notes = gedcom.note_set # "HUSB" - family.husbands = [] + family.husbands.clear() family.husbands.add(*__objects_from_entry_tag(people, entry, 'HUSB')) # "WIFE" - family.wives = [] + family.wives.clear() family.wives.add(*__objects_from_entry_tag(people, entry, 'WIFE')) # "CHIL" - family.children = [] + family.children.clear() family.children.add(*__objects_from_entry_tag(people, entry, 'CHIL')) # "NOTE" - family.notes = [] + family.notes.clear() family.notes.add(*__objects_from_entry_tag(notes, entry, 'NOTE')) family.save() @@ -257,7 +264,7 @@ def __process_Note(entry, g): def __process_Document(entry, obj, g): full_name = __child_value_by_tags(entry, 'FILE') - name = path.basename(full_name).decode('utf-8').strip() + name = path.basename(full_name).strip() known = Document.objects.filter(docfile=name).exists() if not known and not gedcom_storage.exists(name): @@ -275,8 +282,8 @@ def __process_Document(entry, obj, g): make_thumbnail(name, 'w128h128') make_thumbnail(name, 'w640h480') except Exception as e: - print e - print ' Warning: failed to make or find thumbnail: %s' % name + print(e) + print(' Warning: failed to make or find thumbnail: %s' % name) return None # Bail on document creation if thumb fails m.save() @@ -352,7 +359,7 @@ def __objects_from_entry_tag(qset, entry, tag): def __child_value_by_tags(entry, tags, default=None): - if isinstance(tags, basestring): + if isinstance(tags, str): tags = [tags] tags.reverse() next = entry diff --git a/gedgo/middleware.py b/gedgo/middleware.py index bd1ed43..e60554a 100644 --- a/gedgo/middleware.py +++ b/gedgo/middleware.py @@ -1,4 +1,3 @@ -from gedgo import redis import json import time import re @@ -16,40 +15,5 @@ class SimpleTrackerMiddleware(object): """ def process_response(self, request, response): - # Don't process if redis isn't configured or non-200 response - if redis is None or response.status_code != 200: - return response - - # Only track non-superuser visitors - if request.user is None or request.user.is_superuser \ - or not request.user.username: - return response - - for pattern in IGNORE_PATTERNS: - if pattern.match(request.path_info): - return response - - # Increment counters and record pageview. - # This is pretty fast, but could be done in a celery task to reduce - # per-page overhead. - id_ = request.user.id - _increment_key('gedgo_page_view_count') - _increment_key('gedgo_user_%d_page_view_count' % id_) - - page_view = { - 'ip': request.META['REMOTE_ADDR'], - 'path': request.path_info, - 'time': int(time.time()) - } - redis.lpush('gedgo_user_%d_page_views' % id_, json.dumps(page_view)) - redis.ltrim('gedgo_user_%d_page_views' % id_, 0, 100) - + # TODO: Add user tracking return response - - -def _increment_key(key_name): - try: - pvc = int(redis.get(key_name)) - except TypeError: - pvc = 0 - redis.set(key_name, pvc + 1) diff --git a/gedgo/migrations/0004_auto_20210526_1717.py b/gedgo/migrations/0004_auto_20210526_1717.py new file mode 100644 index 0000000..2eb02ea --- /dev/null +++ b/gedgo/migrations/0004_auto_20210526_1717.py @@ -0,0 +1,38 @@ +# Generated by Django 3.0 on 2021-05-26 17:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gedgo', '0003_comment'), + ] + + operations = [ + migrations.AlterField( + model_name='comment', + name='upload', + field=models.FileField(blank=True, null=True, upload_to='uploads/comments'), + ), + migrations.AlterField( + model_name='document', + name='docfile', + field=models.FileField(upload_to='uploads'), + ), + migrations.AlterField( + model_name='document', + name='kind', + field=models.CharField(choices=[('DOCUM', 'Document'), ('VIDEO', 'Video'), ('PHOTO', 'Image')], max_length=5), + ), + migrations.AlterField( + model_name='event', + name='date_approxQ', + field=models.BooleanField(verbose_name='Date is approximate'), + ), + migrations.AlterField( + model_name='family', + name='kind', + field=models.CharField(blank=True, max_length=10, null=True, verbose_name='Event'), + ), + ] diff --git a/gedgo/models/__init__.py b/gedgo/models/__init__.py index 3945ab1..553cac7 100644 --- a/gedgo/models/__init__.py +++ b/gedgo/models/__init__.py @@ -1,12 +1,12 @@ -from gedcom import Gedcom -from comment import Comment -from person import Person -from family import Family -from event import Event -from note import Note -from document import Document -from documentary import Documentary -from blogpost import BlogPost +from .gedcom import Gedcom +from .comment import Comment +from .person import Person +from .family import Family +from .event import Event +from .note import Note +from .document import Document +from .documentary import Documentary +from .blogpost import BlogPost __all__ = [ 'Gedcom', 'Comment', 'Person', 'Family', 'Event', diff --git a/gedgo/models/blogpost.py b/gedgo/models/blogpost.py index 22bcc01..3893582 100644 --- a/gedgo/models/blogpost.py +++ b/gedgo/models/blogpost.py @@ -20,5 +20,5 @@ class Meta: blank=True ) - def __unicode__(self): + def str(self): return 'Blogpost "%s"' % self.title diff --git a/gedgo/models/comment.py b/gedgo/models/comment.py index 38ce10b..9018a09 100644 --- a/gedgo/models/comment.py +++ b/gedgo/models/comment.py @@ -6,15 +6,14 @@ class Comment(models.Model): class Meta: app_label = 'gedgo' - user = models.ForeignKey(User) + user = models.ForeignKey(User, on_delete=models.CASCADE) text = models.TextField() posted = models.DateTimeField(auto_now_add=True) - upload = models.FileField(upload_to='uploads/comments', null=True, - blank=True) + upload = models.FileField(upload_to='uploads/comments', null=True, blank=True) - gedcom = models.ForeignKey('Gedcom', null=True, blank=True) - person = models.ForeignKey('Person', null=True, blank=True) - blogpost = models.ForeignKey('BlogPost', null=True, blank=True) + gedcom = models.ForeignKey('Gedcom', null=True, blank=True, on_delete=models.CASCADE) + person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE) + blogpost = models.ForeignKey('BlogPost', null=True, blank=True, on_delete=models.CASCADE) @property def noun(self): @@ -24,5 +23,5 @@ def noun(self): return self.person return self.gedcom - def __unicode__(self): + def __str__(self): return 'Comment about %s by %s (%d)' % (self.noun, self.user, self.id) diff --git a/gedgo/models/document.py b/gedgo/models/document.py index 6210889..ad5e5ed 100644 --- a/gedgo/models/document.py +++ b/gedgo/models/document.py @@ -10,7 +10,7 @@ class Meta: description = models.TextField(null=True, blank=True) docfile = models.FileField(upload_to='uploads') last_updated = models.DateTimeField(auto_now_add=True) - gedcom = models.ForeignKey('Gedcom', null=True, blank=True) + gedcom = models.ForeignKey('Gedcom', null=True, blank=True, on_delete=models.CASCADE) kind = models.CharField( max_length=5, @@ -29,7 +29,7 @@ class Meta: related_name='media_tagged_families', blank=True ) - def __unicode__(self): + def __str__(self): return path.basename(self.docfile.path) @property diff --git a/gedgo/models/documentary.py b/gedgo/models/documentary.py index bf2d000..1e6bf1a 100644 --- a/gedgo/models/documentary.py +++ b/gedgo/models/documentary.py @@ -10,13 +10,14 @@ class Meta: tagline = models.CharField(max_length=100) location = models.CharField(max_length=100, null=True, blank=True) description = models.TextField(null=True, blank=True) - gedcom = models.ForeignKey('Gedcom') + gedcom = models.ForeignKey('Gedcom', on_delete=models.CASCADE) last_updated = models.DateTimeField(auto_now_add=True) thumb = models.ForeignKey( 'Document', related_name='documentaries_thumb', - blank=True + blank=True, + on_delete=models.CASCADE, ) tagged_people = models.ManyToManyField( 'Person', @@ -29,5 +30,5 @@ class Meta: blank=True ) - def __unicode__(self): + def __str__(self): return self.title diff --git a/gedgo/models/event.py b/gedgo/models/event.py index dc4b018..7a147a0 100644 --- a/gedgo/models/event.py +++ b/gedgo/models/event.py @@ -5,7 +5,7 @@ class Event(models.Model): class Meta: app_label = 'gedgo' - gedcom = models.ForeignKey('Gedcom') + gedcom = models.ForeignKey('Gedcom', on_delete=models.CASCADE) # Can't use DateFields because sometimes only a Year is known, and # we don't want to show those as January 01, , and datetime @@ -28,5 +28,5 @@ def date_string(self): new_date = date(self.date.year, self.date.month, self.date.day) return new_date.strftime(self.date_format) - def __unicode__(self): + def __str__(self): return '%s (%s)' % (self.date_string. self.id) diff --git a/gedgo/models/family.py b/gedgo/models/family.py index 4bf90ff..9459dae 100644 --- a/gedgo/models/family.py +++ b/gedgo/models/family.py @@ -1,14 +1,14 @@ from django.db import models -from document import Document -from documentary import Documentary +from .document import Document +from .documentary import Documentary class Family(models.Model): class Meta: app_label = 'gedgo' pointer = models.CharField(max_length=10, primary_key=True) - gedcom = models.ForeignKey('Gedcom') + gedcom = models.ForeignKey('Gedcom', on_delete=models.CASCADE) last_changed = models.DateField(null=True, blank=True) husbands = models.ManyToManyField('Person', related_name='family_husbands') @@ -22,16 +22,18 @@ class Meta: 'Event', related_name='family_joined', blank=True, - null=True + null=True, + on_delete=models.CASCADE, ) separated = models.ForeignKey( 'Event', related_name='family_separated', blank=True, - null=True + null=True, + on_delete=models.CASCADE, ) - def __unicode__(self): + def __str__(self): return '%s (%s)' % (self.family_name, self.pointer) @property diff --git a/gedgo/models/gedcom.py b/gedgo/models/gedcom.py index e6cd9ee..02a6d6e 100644 --- a/gedgo/models/gedcom.py +++ b/gedgo/models/gedcom.py @@ -1,5 +1,5 @@ from django.db import models -from person import Person +from gedgo.models.person import Person class Gedcom(models.Model): @@ -22,7 +22,7 @@ class Meta: blank=True ) - def __unicode__(self): + def __str__(self): if not self.title: return 'Gedcom #%d' % self.id return '%s (%d)' % (self.title, self.id) diff --git a/gedgo/models/note.py b/gedgo/models/note.py index 0055392..f6c1a0a 100644 --- a/gedgo/models/note.py +++ b/gedgo/models/note.py @@ -6,9 +6,9 @@ class Meta: app_label = 'gedgo' pointer = models.CharField(max_length=10, primary_key=True) text = models.TextField() - gedcom = models.ForeignKey('Gedcom') + gedcom = models.ForeignKey('Gedcom', on_delete=models.CASCADE) - def __unicode__(self): + def __str__(self): return 'Note (%s)' % self.pointer @property diff --git a/gedgo/models/person.py b/gedgo/models/person.py index 60f2304..8c43ff6 100644 --- a/gedgo/models/person.py +++ b/gedgo/models/person.py @@ -1,7 +1,7 @@ from django.db import models -from document import Document -from documentary import Documentary +from gedgo.models.document import Document +from gedgo.models.documentary import Documentary import re @@ -10,7 +10,7 @@ class Meta: app_label = 'gedgo' verbose_name_plural = 'People' pointer = models.CharField(max_length=10, primary_key=True) - gedcom = models.ForeignKey('Gedcom') + gedcom = models.ForeignKey('Gedcom', on_delete=models.CASCADE) last_changed = models.DateField(null=True, blank=True) # Name @@ -28,13 +28,15 @@ class Meta: 'Event', related_name='person_birth', null=True, - blank=True + blank=True, + on_delete=models.CASCADE, ) death = models.ForeignKey( 'Event', related_name='person_death', null=True, - blank=True + blank=True, + on_delete=models.CASCADE, ) # Family @@ -42,7 +44,8 @@ class Meta: 'Family', related_name='person_child_family', null=True, - blank=True + blank=True, + on_delete=models.CASCADE, ) spousal_families = models.ManyToManyField( 'Family', @@ -55,7 +58,7 @@ class Meta: # Profile profile = models.ManyToManyField('Document', blank=True) - def __unicode__(self): + def __str__(self): return '%s, %s (%s)' % (self.last_name, self.first_name, self.pointer) @property diff --git a/gedgo/static/js/pedigree.js b/gedgo/static/js/pedigree.js index 7ab8008..d52af7a 100644 --- a/gedgo/static/js/pedigree.js +++ b/gedgo/static/js/pedigree.js @@ -1,70 +1,71 @@ /* global d3 */ 'use strict'; -const gid = d3.select("#pedigree-tree").attr("data-gid"), - pid = d3.select("#pedigree-tree").attr("data-pid"); +(function() { + const gid = d3.select("#pedigree-tree").attr("data-gid"), + pid = d3.select("#pedigree-tree").attr("data-pid"); -d3.json("/gedgo/" + gid + "/pedigree/" + pid + "/", (treeData) => { + d3.json("/gedgo/" + gid + "/pedigree/" + pid + "/", (treeData) => { - // Create a svg canvas - const vis = d3.select("#pedigree-tree").append("svg:svg") - .attr("width", 480) - .attr("height", 600) - .append("svg:g") - .attr("transform", "translate(40, -100)"); + // Create a svg canvas + const vis = d3.select("#pedigree-tree").append("svg:svg") + .attr("width", 480) + .attr("height", 600) + .append("svg:g") + .attr("transform", "translate(40, -100)"); - // Create a tree "canvas" - const gid = treeData.gid, - tree = d3.layout.tree() - .size([800,230]); + // Create a tree "canvas" + const gid = treeData.gid, + tree = d3.layout.tree() + .size([800,230]); - const diagonal = d3.svg.diagonal() - // change x and y (for the left to right tree) - .projection(d => [d.y, d.x]); + const diagonal = d3.svg.diagonal() + // change x and y (for the left to right tree) + .projection(d => [d.y, d.x]); - // Preparing the data for the tree layout, convert data into an array of nodes - const nodes = tree.nodes(treeData); + // Preparing the data for the tree layout, convert data into an array of nodes + const nodes = tree.nodes(treeData); - // Create an array with all the links - const links = tree.links(nodes); + // Create an array with all the links + const links = tree.links(nodes); - vis.selectAll("pathlink") - .data(links) - .enter().append("svg:path") - .attr("d", diagonal); + vis.selectAll("pathlink") + .data(links) + .enter().append("svg:path") + .attr("d", diagonal); - const node = vis.selectAll("g.node") - .data(nodes) - .enter().append("svg:g") - .attr("transform", d => "translate(" + d.y + "," + d.x + ")"); + const node = vis.selectAll("g.node") + .data(nodes) + .enter().append("svg:g") + .attr("transform", d => "translate(" + d.y + "," + d.x + ")"); - // Add the dot at every node - node.append("svg:rect") - .attr("rx", 10) - .attr("ry", 10) - .attr("y", -30) - .attr("x", -20) - .attr("width", 200) - .attr("height", 50); + // Add the dot at every node + node.append("svg:rect") + .attr("rx", 10) + .attr("ry", 10) + .attr("y", -30) + .attr("x", -20) + .attr("width", 200) + .attr("height", 50); - // place the name atribute left or right depending if children - node.append("svg:a") - .attr("xlink:href", d => "/gedgo/" + gid + "/" + d.id) - .append("text") - .attr("dx", -10) - .attr("dy", -10) - .attr("text-anchor", "start") - .text(d => d.name) - .attr("font-family", "Baskerville") - .attr("font-size", "11pt"); - - node.append("svg:text") - .attr("dx", -10) - .attr("dy", 8) - .attr("text-anchor", "start") - .text(d => d.span) - .attr("font-family", "Baskerville") - .attr("font-size", "11pt") - .attr("fill", "gray"); -}); + // place the name atribute left or right depending if children + node.append("svg:a") + .attr("xlink:href", d => "/gedgo/" + gid + "/" + d.id) + .append("text") + .attr("dx", -10) + .attr("dy", -10) + .attr("text-anchor", "start") + .text(d => d.name) + .attr("font-family", "Baskerville") + .attr("font-size", "11pt"); + node.append("svg:text") + .attr("dx", -10) + .attr("dy", 8) + .attr("text-anchor", "start") + .text(d => d.span) + .attr("font-family", "Baskerville") + .attr("font-size", "11pt") + .attr("fill", "gray"); + }); +})(); diff --git a/gedgo/static/js/timeline.js b/gedgo/static/js/timeline.js index be12c4d..4670049 100644 --- a/gedgo/static/js/timeline.js +++ b/gedgo/static/js/timeline.js @@ -1,62 +1,64 @@ /* global d3 */ 'use strict'; -const gid = d3.select("#timeline").attr("data-gid"), - pid = d3.select("#timeline").attr("data-pid"); +(function() { + const gid = d3.select("#timeline").attr("data-gid"), + pid = d3.select("#timeline").attr("data-pid"); -d3.json("/gedgo/" + gid + "/timeline/" + pid + "/", (data) => { - const events = data.events; - if (events.length < 1) { - $("#timeline-pod").remove(); - return; - } - const birthyear = data.start, - deathyear = data.end, - hscale = d3.scale.linear() - .domain([0, 35]) - .range([20, 400]); + d3.json("/gedgo/" + gid + "/timeline/" + pid + "/", (data) => { + const events = data.events; + if (events.length < 1) { + $("#timeline-pod").remove(); + return; + } + const birthyear = data.start, + deathyear = data.end, + hscale = d3.scale.linear() + .domain([0, 35]) + .range([20, 400]); - //Width and height - const w = 480, - h = hscale(deathyear - birthyear), - scale = d3.scale.linear() - .domain([birthyear, deathyear]) - .range([10, h - 10]); + //Width and height + const w = 480, + h = hscale(deathyear - birthyear), + scale = d3.scale.linear() + .domain([birthyear, deathyear]) + .range([10, h - 10]); - // Create SVG element - const svg = d3.select("#timeline") - .append("svg:svg") - .attr("width", w) - .attr("height", h); + // Create SVG element + const svg = d3.select("#timeline") + .append("svg:svg") + .attr("width", w) + .attr("height", h); - svg.selectAll("line") - .data([1]) - .enter() - .append("line") - .attr("x1", w/2).attr("y1", 10) - .attr("x2", w/2).attr("y2", h - 10) - .attr("stroke", "teal"); + svg.selectAll("line") + .data([1]) + .enter() + .append("line") + .attr("x1", w/2).attr("y1", 10) + .attr("x2", w/2).attr("y2", h - 10) + .attr("stroke", "teal"); - svg.selectAll("circle") - .data(events) - .enter() - .append("circle") - .attr("cx", w/2) - .attr("cy", (d) => scale(d.year)) - .attr("r", 5) - .attr("fill", d => (d.year === birthyear || d.year === deathyear) ? "teal" : "white") - .attr("stroke-width", 3) - .attr("stroke", d => (d.type === 'personal') ? "teal" : "orange"); + svg.selectAll("circle") + .data(events) + .enter() + .append("circle") + .attr("cx", w/2) + .attr("cy", (d) => scale(d.year)) + .attr("r", 5) + .attr("fill", d => (d.year === birthyear || d.year === deathyear) ? "teal" : "white") + .attr("stroke-width", 3) + .attr("stroke", d => (d.type === 'personal') ? "teal" : "orange"); - svg.selectAll("text") - .data(events) - .enter() - .append("text") - .text((d) => d.year + ': ' + d.text) - .attr("x", d => (d.type === 'personal') ? w/2 + 20 : w/2 - 20) - .attr("y", (d) => scale(d.year) + 5) - .attr("text-anchor", d => (d.type === 'personal') ? "start" : "end") - .attr("font-family", "Baskerville") - .attr("font-size", "9pt") - .attr("fill", "gray"); -}); + svg.selectAll("text") + .data(events) + .enter() + .append("text") + .text((d) => d.year + ': ' + d.text) + .attr("x", d => (d.type === 'personal') ? w/2 + 20 : w/2 - 20) + .attr("y", (d) => scale(d.year) + 5) + .attr("text-anchor", d => (d.type === 'personal') ? "start" : "end") + .attr("font-family", "Baskerville") + .attr("font-size", "9pt") + .attr("fill", "gray"); + }); +})(); diff --git a/gedgo/storages.py b/gedgo/storages.py index 805281f..de04f83 100644 --- a/gedgo/storages.py +++ b/gedgo/storages.py @@ -6,8 +6,8 @@ import re import os from PIL import Image -from cStringIO import StringIO -from dropbox.dropbox import Dropbox +from io import StringIO, BytesIO +from dropbox import Dropbox from dropbox.files import FileMetadata, FolderMetadata, ThumbnailFormat, \ ThumbnailSize @@ -26,7 +26,8 @@ def exists(self, name): self.client.files_get_metadata(self.path(name)), (FileMetadata, FolderMetadata) ) - except Exception: + except Exception as e: + print(e) return False def listdir(self, name): @@ -64,7 +65,7 @@ def search(self, query, name='', start=0): return (directories, files) def preview(self, name, size='w128h128'): - file_ = StringIO(self.client.files_get_thumbnail( + file_ = BytesIO(self.client.files_get_thumbnail( self.path(name), format=ThumbnailFormat('jpeg', None), size=ThumbnailSize(size, None) @@ -110,10 +111,10 @@ def resize_thumb(file_, size='w128h128', crop=None): if size in ('w64h64', 'w128h128'): if width > height: - offset = (width - height) / 2 + offset = (width - height) // 2 box = (offset, 0, offset + height, height) else: - offset = ((height - width) * 3) / 10 + offset = ((height - width) * 3) // 10 box = (0, offset, width, offset + width) im = im.crop(box) @@ -121,7 +122,7 @@ def resize_thumb(file_, size='w128h128', crop=None): new_size = [int(d) for d in m.groups()] im.thumbnail(new_size, Image.ANTIALIAS) - output = StringIO() + output = BytesIO() im.save(output, 'JPEG') return output diff --git a/gedgo/tasks.py b/gedgo/tasks.py index d75cff7..d7e34ea 100644 --- a/gedgo/tasks.py +++ b/gedgo/tasks.py @@ -1,37 +1,24 @@ -# flake8: noqa: E402 -from __future__ import absolute_import -import os -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') - -import django -django.setup() - from gedgo.gedcom_update import update -from gedgo import redis from gedgo.models import Gedcom -from celery import Celery from django.conf import settings from datetime import datetime from django.core.mail import send_mail, send_mass_mail from django.contrib.auth.models import User import traceback -import requests -import json - -app = Celery('gedgo') -app.config_from_object(settings) -@app.task def async_update(gedcom_id, file_name, recipient_ids, message, domain, sender_id): + print('OK!') + start = datetime.now() gedcom = Gedcom.objects.get(id=gedcom_id) errstr = '' try: - update(gedcom, file_name, verbose=False) - except Exception: + update(gedcom, file_name, verbose=True) + except Exception as e: + print(e) errstr = traceback.format_exc() end = datetime.now() @@ -61,15 +48,3 @@ def async_update(gedcom_id, file_name, recipient_ids, send_mass_mail(datatuple) - -@app.task -def geo_resolve_ip(ip_address): - if redis is None: - return - try: - response = requests.get('ipinfo.io/%s/json' % ip_address) - j = response.json() - j['requested'] = datetime.utcnow().isoformat() - redis.set('gedgo_ip_%s', json.dumps(j)) - except (requests.exceptions.RequestsException, ValueError): - return diff --git a/gedgo/templates/default/dashboard.html b/gedgo/templates/default/dashboard.html index a9ff142..9c504cf 100644 --- a/gedgo/templates/default/dashboard.html +++ b/gedgo/templates/default/dashboard.html @@ -42,37 +42,6 @@
Users to notify:
After finishing the update, the website will send an email to the site owner indicating that the update is complete.


- -
-

Traffic Stats

- {% if total %} -
-

{{ total }} total page views

- since {{ tracking_start }} -

- {% endif %} - {% if user_views %} -

User Activity

- - - {% for user_view in user_views %} - - - - - - {% endfor %} -
UsernamePage viewsLast View
{{ user_view.user.username }}{{ user_view.count }}{{ user_view.last_view }}
- {% endif %} - {% if not total and not user_views %} -

No activity recorded yet.

-
- {% endif %} -
- {% csrf_token %} - -
-
{% endblock %} {% block javascript %} diff --git a/gedgo/templates/default/gedcom.html b/gedgo/templates/default/gedcom.html index ee1b964..aca2363 100644 --- a/gedgo/templates/default/gedcom.html +++ b/gedgo/templates/default/gedcom.html @@ -4,7 +4,7 @@ {% block leftsidebar %}
{% for photo in gedcom.photo_sample %} -
+
diff --git a/gedgo/templates/default/person.html b/gedgo/templates/default/person.html index b916b45..bb54125 100644 --- a/gedgo/templates/default/person.html +++ b/gedgo/templates/default/person.html @@ -4,7 +4,7 @@ {% block leftsidebar %}
{% for photo in photos %} -
+
@@ -63,7 +63,7 @@

Siblings

{% for family in person.spousal_families.iterator %}
-

{% if family.kind = "MARR" %}Marital Family{% else %}Domestic Relationship{% endif %}

+

{% if family.kind == "MARR" %}Marital Family{% else %}Domestic Relationship{% endif %}

{% for somebody in family.spouses %} {% if not somebody == person %} @@ -78,9 +78,9 @@

{% if family.kind = "MARR" %}Marital Family{% else %}Domestic Relationship{% {% if family.joined.date or family.joined.place or family.separated.date %}
{% if family.joined.date %} - {% if family.kind = "MARR" %}Married{% else %}Domestic Partners{% endif %}: {{ family.joined.date_string }}{% if family.joined.date_approxQ %} (approximate){% endif %}{% endif %}{% if family.joined.place %}
{{ family.joined.place }}{% endif %} + {% if family.kind == "MARR" %}Married{% else %}Domestic Partners{% endif %}: {{ family.joined.date_string }}{% if family.joined.date_approxQ %} (approximate){% endif %}{% endif %}{% if family.joined.place %}
{{ family.joined.place }}{% endif %} {% if family.separated.date %} - {% if family.kind = "MARR" %}Divorced{% else %}Separated{% endif %}: {{ family.separated.date_string }} {% if family.separated.date_approxQ %}(approximate){% endif %} + {% if family.kind == "MARR" %}Divorced{% else %}Separated{% endif %}: {{ family.separated.date_string }} {% if family.separated.date_approxQ %}(approximate){% endif %}
{% endif %} {% if family.separated.place %} diff --git a/gedgo/templates/default/user_tracking.html b/gedgo/templates/default/user_tracking.html deleted file mode 100644 index 57e50ae..0000000 --- a/gedgo/templates/default/user_tracking.html +++ /dev/null @@ -1,21 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
-

{{ user.username }} Recent Activity

- - - - - - - {% for view in views %} - - - - - - {% endfor %} -
PageTimeAddress
{{ view.path|truncatechars:28 }}{{ view.timestamp }}{{ view.ip }}
-
-{% endblock %} diff --git a/gedgo/templates/auth/login.html b/gedgo/templates/registration/login.html similarity index 100% rename from gedgo/templates/auth/login.html rename to gedgo/templates/registration/login.html diff --git a/gedgo/urls.py b/gedgo/urls.py index 792f229..12d083a 100644 --- a/gedgo/urls.py +++ b/gedgo/urls.py @@ -1,7 +1,6 @@ from django.conf.urls import url from django.shortcuts import redirect -from django.contrib.auth.views import password_reset, password_reset_done, \ - password_reset_confirm +from django.contrib.auth.views import PasswordResetView from gedgo import views @@ -27,29 +26,14 @@ url(r'^research/(?P.*)$', views.research), url(r'^search/$', views.search), url(r'^dashboard/$', views.dashboard), - url(r'^dashboard/user/(?P\d+)/$', views.user_tracking), # Auth url(r'^logout/$', views.logout_view), - url(r'^password_reset/$', - password_reset, - { - 'template_name': 'auth/login.html', - 'email_template_name': 'auth/password_reset_email.html', - 'post_reset_redirect': '/gedgo/password_reset/done/' - }), - url(r'^password_reset/done/$', - password_reset_done, - { - 'template_name': 'auth/password_reset_done.html' - }), - url(r'^password_reset/(?P[0-9A-Za-z]+)-(?P.+)/$', - password_reset_confirm, - { - 'post_reset_redirect': '/', - 'template_name': 'auth/password_reset_confirm.html' - }), - + url(r'^password_reset/$', PasswordResetView.as_view( + subject_template_name='email/password_reset_subject.txt', + email_template_name='auth/password_reset_email.txt', + html_email_template_name='email/password_reset.html', + ), name='auth_password_reset'), # Authenticated media fileserve view url(r'^media/(?P\w+)/(?P.*)$', views.media), diff --git a/gedgo/views/__init__.py b/gedgo/views/__init__.py index 793e10e..1796beb 100644 --- a/gedgo/views/__init__.py +++ b/gedgo/views/__init__.py @@ -1,16 +1,16 @@ -from search import search -from model_views import person, gedcom, documentaries, document, \ +from .search import search +from .model_views import person, gedcom, documentaries, document, \ documentary_by_id -from dashboard import dashboard, user_tracking, worker_status -from blog import blog, blog_list, blogpost -from research import research -from visualizations import pedigree, timeline -from util import logout_view -from media import media +from .dashboard import dashboard, worker_status +from .blog import blog, blog_list, blogpost +from .research import research +from .visualizations import pedigree, timeline +from .util import logout_view +from .media import media __all__ = [ 'person', 'gedcom', 'dashboard', 'search', 'documentaries', 'blogpost', 'document', 'documentary_by_id', 'blog', 'blog_list', 'media', 'research', 'logout_view', - 'pedigree', 'timeline', 'user_tracking', 'worker_status' + 'pedigree', 'timeline', 'worker_status' ] diff --git a/gedgo/views/dashboard.py b/gedgo/views/dashboard.py index 526484e..c74cd25 100644 --- a/gedgo/views/dashboard.py +++ b/gedgo/views/dashboard.py @@ -1,8 +1,9 @@ from gedgo.forms import UpdateForm -from gedgo.tasks import app, async_update +from gedgo.tasks import async_update from gedgo.views.util import render from gedgo.models import Gedcom -from gedgo import redis + +from django_simple_task import defer from django.http import Http404, HttpResponse from django.contrib.auth.decorators import login_required @@ -25,10 +26,7 @@ def dashboard(request): raise Http404 form = None - if request.POST and request.GET.get('reset_tracking'): - _reset_tracking() - return redirect('/gedgo/dashboard/') - elif request.POST: + if request.POST: form = UpdateForm(request.POST, request.FILES) _handle_upload(request, form) return redirect('/gedgo/dashboard/') @@ -36,19 +34,13 @@ def dashboard(request): if form is None: form = UpdateForm() - # Collect tracking stats from redis storage - tracking_start, user_views, total = _page_view_stats() - # Render list page with the documents and the form return render( request, 'dashboard.html', { 'form': form, - 'tracking_start': tracking_start, 'users': User.objects.filter(email__contains='@').iterator(), - 'user_views': user_views, - 'total': total, 'gedcoms': Gedcom.objects.iterator() } ) @@ -60,7 +52,7 @@ def worker_status(request): XHR view for whether the celery worker is up """ try: - status = app.control.ping() or [] + status = [True] except Exception: # TODO: What celery exceptions are we catching here? status = [] @@ -70,47 +62,25 @@ def worker_status(request): ) -@login_required -def user_tracking(request, user_id): - if not request.user.is_superuser: - raise Http404 - - user = get_object_or_404(User, id=user_id) - count = redis.keys('gedgo_user_%d_page_view_count' % user.id) - if not count: - raise Http404 - - views = redis.lrange('gedgo_user_%d_page_views' % user.id, 0, -1) - views = [_load_page_view(v) for v in views] - - return render( - request, - 'user_tracking.html', - { - 'user': user, - 'count': count, - 'views': views - } - ) - - def _handle_upload(request, form): if form.is_valid(): file_name = 'uploads/gedcoms/%d_%s' % ( time.time(), form.cleaned_data['gedcom_file'].name) default_storage.save(file_name, form.cleaned_data['gedcom_file']) - async_update.delay( - form.cleaned_data['gedcom_id'], - os.path.join(settings.MEDIA_ROOT, file_name), - form.cleaned_data['email_users'], - form.cleaned_data['message'], - request.get_host(), - request.user.id - ) + defer(async_update, { + 'args': [ + form.cleaned_data['gedcom_id'], + os.path.join(settings.MEDIA_ROOT, file_name), + form.cleaned_data['email_users'], + form.cleaned_data['message'], + request.get_host(), + request.user.id, + ] + }) messages.success( request, 'Your gedcom file has been uploaded and the database will ' - 'be updated momentarily.' + 'be processed shortly.' ) else: error_message = ('Something went wrong with your upload, ' @@ -118,53 +88,3 @@ def _handle_upload(request, form): if hasattr(form, 'error_message'): error_message = form.error_message messages.error(request, error_message) - - -def _reset_tracking(): - if redis is None: - return {} - - keys = redis.keys('gedgo_*') - for key in keys: - redis.delete(key) - - redis.set('gedgo_tracking_start', int(time.time())) - - -def _page_view_stats(): - if redis is None: - return datetime.datetime.utcnow(), {}, 0 - - user_keys = redis.keys('gedgo_user_*_page_view_count') - users = User.objects.filter( - id__in=[int(k.split('_')[2]) for k in user_keys] - ) - - user_views = [] - for user in users: - last = redis.lrange('gedgo_user_%d_page_views' % user.id, 0, 0)[0] - pvc = redis.get('gedgo_user_%d_page_view_count' % user.id) - user_views.append({ - 'user': user, - 'last_view': _load_page_view(last)['timestamp'], - 'count': pvc - }) - user_views = sorted(user_views, key=lambda x: x['last_view'], reverse=True) - - tracking_start = _timestamp_from_redis('gedgo_tracking_start') - - return tracking_start, user_views, redis.get('gedgo_page_view_count') - - -def _load_page_view(json_str): - view = json.loads(json_str) - view['timestamp'] = datetime.datetime.fromtimestamp(int(view['time'])) - return view - - -def _timestamp_from_redis(key): - try: - timestamp = redis.get(key) - return datetime.datetime.fromtimestamp(int(timestamp)) - except Exception: - pass diff --git a/gedgo/views/media.py b/gedgo/views/media.py index 00930db..81570dd 100644 --- a/gedgo/views/media.py +++ b/gedgo/views/media.py @@ -52,13 +52,13 @@ def serve_thumbnail(request, storage_name, storage, size, name): try: if not default_storage.exists(cache_name): - print 'generating cache: ' + cache_name + print('generating cache: ' + cache_name) content = storage.preview(name, size) assert content default_storage.save(cache_name, content) return serve_content(default_storage, cache_name) except Exception as e: - print e + print(e) return HttpResponseRedirect(settings.STATIC_URL + 'img/question.jpg') @@ -83,7 +83,7 @@ def serve_content(storage, name): # Set various file headers and return base = path.basename(name) response['Content-Type'] = mimetypes.guess_type(base)[0] - response['Content-Length'] = storage.size(name) + # response['Content-Length'] = storage.size(name) response['Cache-Control'] = 'public, max-age=31536000' if response['Content-Type'] is None: response['Content-Disposition'] = "attachment; filename=%s;" % (base) diff --git a/gedgo/views/util.py b/gedgo/views/util.py index 4a5ad30..9337594 100644 --- a/gedgo/views/util.py +++ b/gedgo/views/util.py @@ -2,7 +2,7 @@ from django.shortcuts import redirect from django.contrib.auth import logout from django.contrib import messages -from django.shortcuts import render_to_response +from django.shortcuts import render as render_to_response from django.template import RequestContext from gedgo.models import BlogPost, Documentary @@ -30,7 +30,7 @@ def process_comments(request): 'Your comment has been sent. Thank you!' ) except Exception as e: - print e + print(e) messages.error( request, "We're sorry, we couldn't process your comment." @@ -39,10 +39,11 @@ def process_comments(request): def render(request, template, context): + context.update(site_context(request)) return render_to_response( + request, template, context, - context_instance=RequestContext(request, site_context(request)) ) @@ -54,10 +55,7 @@ def site_context(request): """ show_blog = BlogPost.objects.exists() show_documentaries = Documentary.objects.exists() - show_researchfiles = isinstance( - getattr(settings, 'GEDGO_RESEARCH_FILE_ROOT', None), - basestring - ) + show_researchfiles = getattr(settings, 'GEDGO_RESEARCH_FILE_ROOT', None) show_file_uploads = getattr( settings, 'GEDGO_ALLOW_FILE_UPLOADS', True) is True site_title = settings.GEDGO_SITE_TITLE diff --git a/gedgo/views/visualizations.py b/gedgo/views/visualizations.py index 76d912f..31a34bf 100644 --- a/gedgo/views/visualizations.py +++ b/gedgo/views/visualizations.py @@ -220,5 +220,7 @@ def __gatherby(inlist, func): ['Wikipedia founded', 2001], ['Human genome project completed', 2003], ['Barack Obama sworn US President', 2009], - ['population reaches 7 billion', 2011] + ['population reaches 7 billion', 2011], + ['NASA Flies by Pluto', 2015], + ['COVID-19 Pandemic', 2020], ] diff --git a/reqs.frozen.pip b/reqs.frozen.pip index b1bd0c1..99ce993 100644 --- a/reqs.frozen.pip +++ b/reqs.frozen.pip @@ -1,31 +1,31 @@ -amqp==2.2.2 -anyjson==0.3.3 -billiard==3.5.0.3 -celery==4.1.0 -certifi==2018.1.18 -chardet==3.0.4 -configparser==3.5.0 -dj-static==0.0.6 -Django==1.9.2 -dropbox==8.6.0 -enum34==1.1.6 -flake8==3.5.0 -funcsigs==1.0.2 -gunicorn==19.7.1 -idna==2.6 -kombu==4.1.0 -mccabe==0.6.1 -mock==2.0.0 -MySQL-python==1.2.5 -pbr==3.1.1 -Pillow==3.1.1 -pycodestyle==2.3.1 -pyflakes==1.6.0 -pytz==2017.3 -redis==2.10.6 -requests==2.18.4 -six==1.11.0 -static3==0.7.0 -urllib3==1.22 -vine==1.1.4 -virtualenv==15.1.0 +DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. +amqp==2.2.2 +anyjson==0.3.3 +billiard==3.5.0.3 +celery==4.1.0 +certifi==2018.1.18 +chardet==3.0.4 +configparser==3.5.0 +dj-static==0.0.6 +Django==1.9.2 +dropbox==8.6.0 +enum34==1.1.6 +flake8==3.5.0 +funcsigs==1.0.2 +gunicorn==19.7.1 +idna==2.6 +kombu==4.1.0 +mccabe==0.6.1 +mock==2.0.0 +MySQL-python==1.2.5 +pbr==3.1.1 +Pillow==3.1.1 +pycodestyle==2.3.1 +pyflakes==1.6.0 +pytz==2017.3 +requests==2.18.4 +six==1.11.0 +static3==0.7.0 +urllib3==1.22 +vine==1.1.4 +virtualenv==15.1.0 diff --git a/reqs.pip b/reqs.pip index 3b4c884..d10e30d 100644 --- a/reqs.pip +++ b/reqs.pip @@ -1,12 +1,10 @@ -django==1.9.2 -celery==4.1.0 +django==3.2.3 pillow==3.1.1 -dj-static==0.0.6 -gunicorn -anyjson -dropbox -mysql-python -redis -flake8 -mock +python-dateutil +uvicorn requests +mock +flake8 +anyjson +dropbox==9.0.0 +django-simple-task diff --git a/settings.py b/settings.py index 939ff33..14c5817 100644 --- a/settings.py +++ b/settings.py @@ -1,35 +1,20 @@ import os -import sys -project_root = os.path.dirname(__file__) -# Django settings for gedgo project. - -DEBUG = True - -ADMINS = () -MANAGERS = ADMINS +# Django settings for gedgo project. DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'gedgo', - 'USER': 'gedgo', - 'PASSWORD': 'gedgo', - 'HOST': 'db', - 'PORT': '', + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': '/data/db.sqllite', } } -ALLOWED_HOSTS = [] -TIME_ZONE = 'America/New_York' -LANGUAGE_CODE = 'en-us' - SITE_ID = 1 USE_I18N = True USE_L10N = True USE_TZ = True -MEDIA_ROOT = '/app/files/default/' +MEDIA_ROOT = '/app/.files/default/' MEDIA_URL = '/gedgo/media/default/' STATIC_ROOT = '/static/' @@ -46,8 +31,8 @@ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ - os.path.join(project_root, 'gedgo/templates'), - os.path.join(project_root, 'gedgo/templates/default'), + '/app/gedgo/templates', + '/app/gedgo/templates/default', ], 'APP_DIRS': True, 'OPTIONS': { @@ -61,13 +46,14 @@ 'django.template.context_processors.static', 'django.template.context_processors.tz', 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.request', ], }, }, ] -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -78,7 +64,7 @@ ) ROOT_URLCONF = 'urls' -WSGI_APPLICATION = 'wsgi.application' +ASGI_APPLICATION = 'asgi.application' INSTALLED_APPS = ( 'django.contrib.auth', @@ -88,13 +74,15 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', - 'gedgo' + 'django_simple_task', + 'gedgo', ) + CACHES = { 'research_preview': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', - 'LOCATION': '/app/files/research_preview', + 'LOCATION': '/app/.files/research_preview', }, 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', @@ -104,27 +92,9 @@ MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' -# Just send emails to the console. -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -SERVER_EMAIL = ['noreply@example.com'] - -GEDGO_ALLOW_FILE_UPLOADS = True -GEDGO_SENDFILE_HEADER = 'X-Accel-Redirect' -GEDGO_SENDFILE_PREFIX = '/protected/' -GEDGO_SITE_TITLE = 'My Genealogy Site' -GEDGO_REDIS_SERVER = 'redis' -GEDGO_RESEARCH_FILE_STORAGE = 'gedgo.storages.FileSystemSearchableStorage' -GEDGO_RESEARCH_FILE_ROOT = '/app/files/gedcom/' -GEDGO_DOCUMENTARY_STORAGE = 'gedgo.storages.FileSystemSearchableStorage' -GEDGO_DOCUMENTARY_ROOT = '/app/files/documentaries/' -GEDGO_GEDCOM_FILE_STORAGE = 'gedgo.storages.FileSystemSearchableStorage' -GEDGO_GEDCOM_FILE_ROOT = '/app/files/research/' -GEDGO_SHOW_RESEARCH_FILES = True - -BROKER_BACKEND = 'redis' -BROKER_URL = 'redis://redis:6379/0' -CELERY_RESULT_BACKEND = 'redis://redis:6379/0' -CELERY_ACCEPT_CONTENT = ["json"] +# BROKER_URL = 'django://' +# CELERY_RESULT_BACKEND = 'djcelery.backends.database' +# CELERY_ACCEPT_CONTENT = ["json"] LOGGING = { 'version': 1, @@ -149,12 +119,52 @@ }, } } +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' + -if 'test' in sys.argv: - DATABASES['default']['USER'] = 'root' - DATABASES['default']['PASSWORD'] = 'docker' +# +# Environment-variable overrides +# + +TIME_ZONE = os.environ.get('TIME_ZONE', 'America/New_York') +LANGUAGE_CODE = os.environ.get('LANGUAGE_CODE', 'en-us') +ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '*').split(',') +DEBUG = (os.environ.get('DEBUG') == 'True') +SECRET_KEY = os.environ.get('SECRET_KEY', 'foo') +if os.environ.get('ADMINS'): + ADMINS = [a.split(':') for a in os.environ['ADMINS'].split(',')] else: - try: - from settings_local import * # noqa - except ImportError: - pass + ADMINS = [] +MANAGERS = ADMINS + +# +# Gedgo-specific settings +# + +DROPBOX_ACCESS_TOKEN = os.environ.get('DROPBOX_ACCESS_TOKEN', None) +GEDGO_ALLOW_FILE_UPLOADS = os.environ.get('GEDGO_ALLOW_FILE_UPLOADS', 'False') == 'True' +GEDGO_SENDFILE_HEADER = os.environ.get('GEDGO_SENDIFLE_HEADER', 'X-Accel-Redirect') +GEDGO_SENDFILE_PREFIX = os.environ.get('GEDOG_SENDFILE_PREFIX', '/protected/') +GEDGO_SITE_TITLE = os.environ.get('GEDGO_SITE_TITLE', 'My Genealogy Site') +GEDGO_RESEARCH_FILE_STORAGE = os.environ.get('GEDGO_RESEARCH_FILE_STORAGE', 'gedgo.storages.FileSystemSearchableStorage') +GEDGO_RESEARCH_FILE_ROOT = os.environ.get('GEDGO_RESEARCH_FILE_ROOT', '/app/.files/gedcom/') +GEDGO_DOCUMENTARY_STORAGE = os.environ.get('GEDGO_DOCUMENTARY_STORAGE', 'gedgo.storages.FileSystemSearchableStorage') +GEDGO_DOCUMENTARY_ROOT = os.environ.get('GEDGO_DOCUMENTARY_ROOT', '/app/.files/documentaries/') +GEDGO_GEDCOM_FILE_STORAGE = os.environ.get('GEDGO_GEDCOM_FILE_STORAGE', 'gedgo.storages.FileSystemSearchableStorage') +GEDGO_GEDCOM_FILE_ROOT = os.environ.get('GEDGO_GEDCOM_FILE_ROOT', '/app/.files/research/') +GEDGO_SHOW_RESEARCH_FILES = os.environ.get('GEDGO_SHOW_RESEARCH_FILES', 'True') == 'True' + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +SERVER_EMAIL = ['noreply@example.com'] +if os.environ.get('EMAIL_HOST') and not DEBUG: + EMAIL_BACKEND = os.environ.get( + 'EMAIL_BACKEND', + 'django.core.mail.backends.smtp.EmailBackend' + ) + EMAIL_USE_TLS = True + EMAIL_PORT = 587 + EMAIL_HOST = os.environ.get('EMAIL_HOST') + EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') + EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') + DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', EMAIL_HOST_USER) + SERVER_EMAIL = os.environ.get('SERVER_EMAIL', EMAIL_HOST_USER) diff --git a/urls.py b/urls.py index 10c6c96..dc76d98 100644 --- a/urls.py +++ b/urls.py @@ -2,17 +2,17 @@ from django.shortcuts import redirect from django.http import HttpResponse from django.contrib import admin -from django.contrib.auth.views import login +from django.contrib.auth.views import LoginView admin.autodiscover() urlpatterns = [ url(r'^$', lambda r: redirect('/gedgo/')), url(r'^gedgo/', include('gedgo.urls')), - url(r'^admin/', include(admin.site.urls)), - url(r'^accounts/login/$', login, + url(r'^admin/', admin.site.urls), + url(r'^accounts/login/$', LoginView.as_view(), {'template_name': 'auth/login.html'}), - url(r'^login/$', login, + url(r'^login/$', LoginView.as_view(), {'template_name': 'auth/login.html'}), url(r'^robots\.txt$', lambda r: HttpResponse( diff --git a/wsgi.py b/wsgi.py deleted file mode 100644 index 6205d83..0000000 --- a/wsgi.py +++ /dev/null @@ -1,7 +0,0 @@ -import os -from django.core.wsgi import get_wsgi_application -from dj_static import Cling - -os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' - -application = Cling(get_wsgi_application())