From 16754dfdd4870946eca2fc71607ea40204330524 Mon Sep 17 00:00:00 2001 From: Greg Thole Date: Sat, 20 Jan 2018 13:09:58 -0500 Subject: [PATCH] Get worker layer functioning again --- Dockerfile | 4 + docker-compose.yml | 4 +- gedgo-web.conf | 1 + gedgo/admin.py | 9 -- gedgo/gedcom_update.py | 116 ++++++++---------- gedgo/management/commands/update_gedcom.py | 3 +- gedgo/migrations/0002_auto_20180120_1030.py | 34 +++++ gedgo/models/document.py | 1 - gedgo/models/event.py | 3 +- gedgo/models/family.py | 2 + gedgo/models/gedcom.py | 13 +- gedgo/models/note.py | 1 - gedgo/models/person.py | 1 + gedgo/static/img/question.jpg | Bin 0 -> 130591 bytes gedgo/static/styles/style-default.css | 4 + gedgo/storages.py | 38 +++++- gedgo/tasks.py | 11 +- .../templates/default/basic-information.html | 37 ++++++ gedgo/templates/default/dashboard.html | 8 +- gedgo/templates/default/document_preview.html | 20 +++ gedgo/templates/default/documentaries.html | 17 ++- .../templates/default/documentary_by_id.html | 31 +++++ gedgo/templates/default/gedcom.html | 4 +- gedgo/templates/default/person-card.html | 4 +- gedgo/templates/default/person.html | 64 +++------- gedgo/templates/default/research.html | 4 +- gedgo/templates/default/research_preview.html | 30 ++--- gedgo/urls.py | 5 +- gedgo/views/__init__.py | 9 +- gedgo/views/dashboard.py | 2 +- gedgo/views/media.py | 89 ++++++++++++++ gedgo/views/model_views.py | 30 ++++- gedgo/views/research.py | 78 ++++-------- gedgo/views/util.py | 53 -------- reqs.pip | 4 +- settings.py | 8 +- 36 files changed, 448 insertions(+), 294 deletions(-) create mode 100644 gedgo/migrations/0002_auto_20180120_1030.py create mode 100644 gedgo/static/img/question.jpg create mode 100644 gedgo/templates/default/basic-information.html create mode 100644 gedgo/templates/default/document_preview.html create mode 100644 gedgo/templates/default/documentary_by_id.html create mode 100644 gedgo/views/media.py diff --git a/Dockerfile b/Dockerfile index 8b07583..2438e1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,3 +4,7 @@ COPY ./ /app/ WORKDIR /app/ RUN pip install -r reqs.pip + +RUN adduser --disabled-password --gecos '' gedgo +RUN chown -R gedgo:gedgo /app +USER gedgo diff --git a/docker-compose.yml b/docker-compose.yml index ea76fdb..74f946b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,8 @@ services: MYSQL_DATABASE: 'gedgo' MYSQL_USER: 'gedgo' MYSQL_PASSWORD: 'gedgo' + volumes: + - './tmp:/gedgo_tmp' logging: driver: 'none' redis: @@ -49,7 +51,7 @@ services: worker: container_name: 'gedgo_worker' image: 'gedgo_app' - command: ['python', 'manage.py', 'celeryd', '-c', '1', '--loglevel=info'] + command: ['celery', '-A', 'gedgo.tasks', 'worker', '--loglevel=debug'] volumes: - './:/app' links: diff --git a/gedgo-web.conf b/gedgo-web.conf index 410c799..958f235 100644 --- a/gedgo-web.conf +++ b/gedgo-web.conf @@ -12,6 +12,7 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; + client_max_body_size 10M; if (!-f $request_filename) { proxy_pass http://app_server; diff --git a/gedgo/admin.py b/gedgo/admin.py index 4189009..3f92f8a 100644 --- a/gedgo/admin.py +++ b/gedgo/admin.py @@ -1,9 +1,6 @@ from gedgo.models import Gedcom, BlogPost, Document, Documentary from django.contrib import admin -from djcelery.models import TaskState, WorkerState, \ - PeriodicTask, IntervalSchedule, CrontabSchedule - class GedcomAdmin(admin.ModelAdmin): exclude = ('key_people',) @@ -31,9 +28,3 @@ class DocumentaryAdmin(admin.ModelAdmin): admin.site.register(BlogPost, BlogPostAdmin) admin.site.register(Document, DocumentAdmin) admin.site.register(Documentary, DocumentaryAdmin) - -admin.site.unregister(TaskState) -admin.site.unregister(WorkerState) -admin.site.unregister(IntervalSchedule) -admin.site.unregister(CrontabSchedule) -admin.site.unregister(PeriodicTask) diff --git a/gedgo/gedcom_update.py b/gedgo/gedcom_update.py index 1f4a017..a6ab291 100644 --- a/gedgo/gedcom_update.py +++ b/gedgo/gedcom_update.py @@ -5,14 +5,11 @@ from django.db import transaction from django.utils.datetime_safe import date from django.utils import timezone -from datetime import datetime +from datetime import datetime, date from re import findall from os import path -from cStringIO import StringIO -from PIL import Image - -from gedgo.storages import gedcom_storage +from gedgo.storages import gedcom_storage, resize_thumb @transaction.atomic @@ -27,7 +24,7 @@ def update(g, file_name, verbose=True): title=__child_value_by_tags(parsed.header, 'TITL', default=''), last_updated=datetime(1920, 1, 1) # TODO: Fix. ) - print g.id + print 'Gedcom id=%s' % g.id if verbose: print 'Importing entries to models' @@ -71,7 +68,7 @@ def __process_all_relations(gedcom, parsed, verbose=True): # Process Person objects for index, person in enumerate(gedcom.person_set.iterator()): entry = parsed.entries.get(person.pointer) - print index + if entry is not None: __process_person_relations(gedcom, person, entry) else: @@ -139,13 +136,18 @@ def __process_family_relations(gedcom, family, entry): # --- Import Constructors def __process_Person(entry, g): - if __check_unchanged(entry, g): - return - p, _ = Person.objects.get_or_create( pointer=entry['pointer'], gedcom=g) + # No changes recorded in the gedcom, skip it + if __check_unchanged(entry, p): + return None + + p.last_changed = __parse_gen_date( + __child_value_by_tags(entry, ['CHAN', 'DATE']) + )[0] + # Name name_value = __child_value_by_tags(entry, 'NAME', default='') name = findall(r'^([^/]*) /([^/]+)/$', name_value) @@ -162,6 +164,8 @@ def __process_Person(entry, g): p.education = __child_value_by_tags(entry, 'EDUC') p.religion = __child_value_by_tags(entry, 'RELI') + p.save() + # Media document_entries = [ c for c in entry.get('children', []) @@ -172,17 +176,20 @@ def __process_Person(entry, g): if (d is not None) and (__child_value_by_tags(m, 'PRIM') == 'Y'): p.profile.add(d) - p.save() def __process_Family(entry, g): - if __check_unchanged(entry, g): - return - f, _ = Family.objects.get_or_create( pointer=entry['pointer'], gedcom=g) + if __check_unchanged(entry, f): + return None + + f.last_changed = __parse_gen_date( + __child_value_by_tags(entry, ['CHAN', 'DATE']) + )[0] + for k in ['MARR', 'DPAR']: f.joined = __create_Event(__child_by_tag(entry, k), g, f.joined) if f.joined: @@ -225,6 +232,7 @@ def __create_Event(entry, g, e): e.date_approxQ = date_approxQ e.save() + return e @@ -243,35 +251,35 @@ def __process_Note(entry, g): n.text = n.text.strip('\n') n.save() + return n def __process_Document(entry, obj, g): - name = __valid_document_entry(entry) - if not name: - return None + full_name = __child_value_by_tags(entry, 'FILE') + name = path.basename(full_name).decode('utf-8').strip() + known = Document.objects.filter(docfile=name).exists() - file_name = 'gedcom/%s' % name - known = Document.objects.filter(docfile=file_name) + if not known and not gedcom_storage.exists(name): + return None - if len(known) > 0: - m = known[0] + kind = __child_value_by_tags(entry, 'TYPE') + if known: + m = Document.objects.filter(docfile=name).first() else: - kind = __child_value_by_tags(entry, 'TYPE') m = Document(gedcom=g, kind=kind) - m.docfile.name = file_name - if kind == 'PHOTO': - try: - make_thumbnail(name, __child_value_by_tags(entry, 'CROP')) - thumb = path.join('default/thumbs', name) - except: - print ' Warning: failed to make or find thumbnail: %s' % name - return None # Bail on document creation if thumb fails - else: - thumb = None - if thumb is not None: - m.thumb.name = thumb - m.save() + m.docfile.name = name + + if kind == 'PHOTO': + try: + make_thumbnail(name, 'w128h128') + make_thumbnail(name, 'w640h480') + except Exception as e: + print e + print ' Warning: failed to make or find thumbnail: %s' % name + return None # Bail on document creation if thumb fails + + m.save() if isinstance(obj, Person) and \ not m.tagged_people.filter(pointer=obj.pointer).exists(): @@ -284,11 +292,12 @@ def __process_Document(entry, obj, g): # --- Helper Functions -def __check_unchanged(entry, g): +def __check_unchanged(entry, existing): changed = __parse_gen_date( __child_value_by_tags(entry, ['CHAN', 'DATE']) )[0] - return changed and g.last_updated and (changed <= g.last_updated) + return isinstance(existing.last_changed, date) and \ + changed == existing.last_changed DATE_FORMATS = [ @@ -361,37 +370,16 @@ def __child_by_tag(entry, tag): return child -def __valid_document_entry(e): - full_name = __child_value_by_tags(e, 'FILE') - name = path.basename(full_name).decode('utf-8').strip() - if gedcom_storage.exists(name): - return name - - -def make_thumbnail(name, crop): +def make_thumbnail(name, size): """ Copies an image from gedcom_storage, converts it to a thumbnail, and saves - it to default_storage for fast access + it to default_storage for fast access. This also gets done on the fly, + but it's better to pre-build """ - thumb_name = path.join('thumbs', name) + thumb_name = path.join('preview-cache', 'gedcom', size, name) if default_storage.exists(thumb_name): return thumb_name - im = Image.open(gedcom_storage.open(name)) - width, height = im.size - - # TODO: Use crop argument - if width > height: - offset = (width - height) / 2 - box = (offset, 0, offset + height, height) - else: - offset = ((height - width) * 3) / 10 - box = (0, offset, width, offset + width) - cropped = im.crop(box) - - size = 150, 150 - cropped.thumbnail(size, Image.ANTIALIAS) - output = StringIO() - cropped.save(output, 'JPEG') - return default_storage.save(thumb_name, output) + resized = resize_thumb(gedcom_storage.open(name), size=size) + return default_storage.save(thumb_name, resized) diff --git a/gedgo/management/commands/update_gedcom.py b/gedgo/management/commands/update_gedcom.py index d16d409..c608076 100644 --- a/gedgo/management/commands/update_gedcom.py +++ b/gedgo/management/commands/update_gedcom.py @@ -45,7 +45,7 @@ def handle(self, *args, **options): # Check file time against gedcom last_update time. file_time = datetime.fromtimestamp(path.getmtime(file_name)) last_update_time = g.last_updated.replace(tzinfo=None) - if (options['force'] or (file_time > last_update_time)): + if (options['force'] or True or (file_time > last_update_time)): start = datetime.now() errstr = '' @@ -55,6 +55,7 @@ def handle(self, *args, **options): e = exc_info()[0] errstr = 'There was an error: %s\n%s' % ( e, traceback.format_exc()) + print errstr end = datetime.now() diff --git a/gedgo/migrations/0002_auto_20180120_1030.py b/gedgo/migrations/0002_auto_20180120_1030.py new file mode 100644 index 0000000..53936d8 --- /dev/null +++ b/gedgo/migrations/0002_auto_20180120_1030.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.2 on 2018-01-20 15:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gedgo', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='document', + name='thumb', + ), + migrations.AddField( + model_name='family', + name='last_changed', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='person', + name='last_changed', + field=models.DateField(blank=True, null=True), + ), + migrations.AlterField( + model_name='document', + name='docfile', + field=models.FileField(upload_to=b'gedcom'), + ), + ] diff --git a/gedgo/models/document.py b/gedgo/models/document.py index 82e33fc..302ffa6 100644 --- a/gedgo/models/document.py +++ b/gedgo/models/document.py @@ -11,7 +11,6 @@ class Meta: docfile = models.FileField(upload_to='gedcom') last_updated = models.DateTimeField(auto_now_add=True) gedcom = models.ForeignKey('Gedcom', null=True, blank=True) - thumb = models.FileField(upload_to='thumbs', null=True, blank=True) kind = models.CharField( max_length=5, diff --git a/gedgo/models/event.py b/gedgo/models/event.py index 1374f7b..dc4b018 100644 --- a/gedgo/models/event.py +++ b/gedgo/models/event.py @@ -5,6 +5,8 @@ class Event(models.Model): class Meta: app_label = 'gedgo' + gedcom = models.ForeignKey('Gedcom') + # Can't use DateFields because sometimes only a Year is known, and # we don't want to show those as January 01, , and datetime # doesn't allow missing values. @@ -12,7 +14,6 @@ class Meta: year_range_end = models.IntegerField(null=True) date_format = models.CharField(null=True, max_length=10) date_approxQ = models.BooleanField('Date is approximate') - gedcom = models.ForeignKey('Gedcom') place = models.CharField(max_length=50) # Breaks strict MVC conventions. diff --git a/gedgo/models/family.py b/gedgo/models/family.py index 9e9c9c0..4bf90ff 100644 --- a/gedgo/models/family.py +++ b/gedgo/models/family.py @@ -9,6 +9,8 @@ class Meta: app_label = 'gedgo' pointer = models.CharField(max_length=10, primary_key=True) gedcom = models.ForeignKey('Gedcom') + last_changed = models.DateField(null=True, blank=True) + husbands = models.ManyToManyField('Person', related_name='family_husbands') wives = models.ManyToManyField('Person', related_name='family_wives') children = models.ManyToManyField('Person', related_name='family_children') diff --git a/gedgo/models/gedcom.py b/gedgo/models/gedcom.py index d46dde1..fb61e7f 100644 --- a/gedgo/models/gedcom.py +++ b/gedgo/models/gedcom.py @@ -1,7 +1,7 @@ from django.db import models import random -from document import Document +from person import Person class Gedcom(models.Model): @@ -31,5 +31,12 @@ def __unicode__(self): @property def photo_sample(self): - photos = Document.objects.filter(gedcom=self, kind='PHOTO') - return random.sample(photos, min(24, len(photos))) + people = Person.objects.filter(gedcom=self).order_by('?') + + sample = [] + for person in people.iterator(): + if person.key_photo: + sample.append(person.key_photo) + if len(sample) == 24: + break + return sample diff --git a/gedgo/models/note.py b/gedgo/models/note.py index 940cad4..0055392 100644 --- a/gedgo/models/note.py +++ b/gedgo/models/note.py @@ -5,7 +5,6 @@ class Note(models.Model): class Meta: app_label = 'gedgo' pointer = models.CharField(max_length=10, primary_key=True) - text = models.TextField() gedcom = models.ForeignKey('Gedcom') diff --git a/gedgo/models/person.py b/gedgo/models/person.py index c3a18d7..1395e45 100644 --- a/gedgo/models/person.py +++ b/gedgo/models/person.py @@ -11,6 +11,7 @@ class Meta: verbose_name_plural = 'People' pointer = models.CharField(max_length=10, primary_key=True) gedcom = models.ForeignKey('Gedcom') + last_changed = models.DateField(null=True, blank=True) # Name first_name = models.CharField(max_length=255) diff --git a/gedgo/static/img/question.jpg b/gedgo/static/img/question.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa0d4bd16c896a1223eeaaae8da81c4a7f81134b GIT binary patch literal 130591 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 Lzt~!i height: + offset = (width - height) / 2 + box = (offset, 0, offset + height, height) + else: + offset = ((height - width) * 3) / 10 + box = (0, offset, width, offset + width) + im = im.crop(box) + + m = re.match('w(\d+)h(\d+)', size) + new_size = [int(d) for d in m.groups()] + + im.thumbnail(new_size, Image.ANTIALIAS) + output = StringIO() + im.save(output, 'JPEG') + return output + research_storage = import_string(settings.GEDGO_RESEARCH_FILE_STORAGE)( location=settings.GEDGO_RESEARCH_FILE_ROOT) diff --git a/gedgo/tasks.py b/gedgo/tasks.py index 523be1a..e135944 100644 --- a/gedgo/tasks.py +++ b/gedgo/tasks.py @@ -1,10 +1,12 @@ - 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 -import os from celery import Celery from django.conf import settings from datetime import datetime @@ -14,10 +16,7 @@ import requests import json - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') - -app = Celery() +app = Celery('gedgo') app.config_from_object(settings) diff --git a/gedgo/templates/default/basic-information.html b/gedgo/templates/default/basic-information.html new file mode 100644 index 0000000..edad476 --- /dev/null +++ b/gedgo/templates/default/basic-information.html @@ -0,0 +1,37 @@ +{% block basic_information %} +

({{ person.year_range }})

+ {% if person.birth or person.death or person.education or person.religion %} + + {% if person.birth.date %} + + + + {% endif %} + {% if person.birth.place %} + + + + {% endif %} + {% if person.death.date %} + + + + {% endif %} + {% if person.death.place %} + + + + {% endif %} + {% if person.education %} + + + + {% endif %} + {% if person.religion %} + + + + {% endif %} +
Born:{{ person.birth.date_string }} {% if person.birth.date_approxQ %}(approximate){% endif %}
Birthplace:{{ person.birth.place }}
Died:{{ person.death.date_string }} {% if person.death.date_approxQ %}(approximate){% endif %}
Deathplace:{{ person.death.place }}
Education:{{ person.education_delimited|linebreaksbr }}
Religion:{{ person.religion }}
+ {% endif %} +{% endblock %} diff --git a/gedgo/templates/default/dashboard.html b/gedgo/templates/default/dashboard.html index 3b954e3..573a40e 100644 --- a/gedgo/templates/default/dashboard.html +++ b/gedgo/templates/default/dashboard.html @@ -1,15 +1,17 @@ {% extends "base.html" %} -{% block content %} -
+{% block leftsidebar %} +

Worker Status

+{% endblock %} +{% block content %} +

Update a Gedcom

-

Update a Gedcom

{% csrf_token %}