Browse Source

Initial import.

master 0.0
Erik Stein 8 years ago
commit
61e0031848
  1. 10
      .gitignore
  2. 3
      CHANGELOG
  3. 27
      LICENSE
  4. 4
      MANIFEST.in
  5. 3
      README
  6. 4
      people/__init__.py
  7. 90
      people/admin.py
  8. 11
      people/apps.py
  9. 20
      people/controllers.py
  10. 83
      people/migrations/0001_initial.py
  11. 0
      people/migrations/__init__.py
  12. 143
      people/models.py
  13. 27
      people/templates/people/person_list.html
  14. 3
      people/tests.py
  15. 13
      people/urls.py
  16. 33
      people/views.py
  17. 35
      requirements.txt
  18. 56
      setup.py

10
.gitignore vendored

@ -0,0 +1,10 @@
*.py?
*.sw?
*~
.coverage
.tox
/*.egg-info
/MANIFEST
build
dist
htmlcov

3
CHANGELOG

@ -0,0 +1,3 @@
## 1.0.0
First public version.

27
LICENSE

@ -0,0 +1,27 @@
Copyright (c) 2016-2017, Erik Stein and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Feinheit AG nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

4
MANIFEST.in

@ -0,0 +1,4 @@
include LICENSE
include MANIFEST.in
include README
recursive-include people/templates *

3
README

@ -0,0 +1,3 @@
# django-people
Models for person and several mixins, group and pseudonym support.

4
people/__init__.py

@ -0,0 +1,4 @@
VERSION = (1, 0, 0)
__version__ = '.'.join(map(str, VERSION))
default_app_config = 'people.apps.PeopleConfig'

90
people/admin.py

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# Erik Stein <code@classlibrary.net>, 2016
from django.contrib import admin
from django.contrib.contenttypes.admin import GenericTabularInline
from django.utils.translation import ugettext_lazy as _
from admin_steroids.options import ImproveRawIdFieldsFormTabularInline
from .models import PersonRole, Person, GenericParticipationRel
@admin.register(PersonRole)
class PersonRoleAdmin(admin.ModelAdmin):
list_display = ['get_name', 'id_text', 'label_de', 'label_en']
list_editable = ['id_text', 'label_de', 'label_en']
search_fields = ['name_de', 'name_en']
def get_name(self, obj):
return obj.name_de or obj.name_en
get_name.short_description = _("Bezeichnung")
get_name.admin_order_field = 'name_de'
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
class GroupMembershipListFilter(admin.SimpleListFilter):
title = _("Gruppe")
parameter_name = 'group'
def lookups(self, request, model_admin):
return Person.objects.filter(is_group=True).values_list('slug', 'name')
def queryset(self, request, queryset):
if self.value():
return queryset.filter(groups__slug=self.value())
else:
return queryset
model = Person
list_display = ('is_group', 'name', 'get_main_person', 'sort_name', 'slug')
list_display_links = ('name',)
list_editable = ('sort_name', 'slug')
list_filter = (
'is_group',
GroupMembershipListFilter,
'_is_main_person',
)
search_fields = ('name',)
fieldsets = (
(None, {
'classes': ('wide',),
'fields': (
('name', 'sort_name'),
'slug',
'main_person',
)
}),
(_("Gruppe/Gruppenmitglieder"), {
'classes': ('wide', 'collapse'),
'fields': (
'is_group',
'members',
)
}),
)
prepopulated_fields = {'slug': ('name',)}
raw_id_fields = ['main_person']
filter_horizontal = ('members',)
def get_groups_display(self, obj):
return ",".join(obj.groups.values_list('name', flat=True))
get_groups_display.short_description = _("Gruppen")
def get_main_person(self, obj):
return getattr(obj.main_person, 'name', "")
get_main_person.short_description = _("Haupteintrag")
class GenericParticipationInline(ImproveRawIdFieldsFormTabularInline, GenericTabularInline):
model = GenericParticipationRel
verbose_name = _("Teilnehmer/in")
verbose_name_plural = _("Teilnehmer/innen")
fields = ('role', 'person', 'label', 'order_index',)
raw_id_fields = ('person',)
related_search_fields = {
'person': ('name',),
}
extra = 0

11
people/apps.py

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# Erik Stein <code@classlibrary.net>, 2016
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class PeopleConfig(AppConfig):
name = 'people'
verbose_name = _("Personen")

20
people/controllers.py

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# Erik Stein <code@classlibrary.net>, 2016
from django.utils.translation import ugettext_lazy as _
class ArtPersonController(object):
def get_lifetime_display(self):
if self.birth_year and self.year_of_death:
return u"%s%s" % (self.birth_year, self.year_of_death)
elif self.birth_year:
return _("geb. %s") % self.birth_year
else:
return ""
get_lifetime_display.short_description = _("Lebensdaten")
class PersonController(object):
pass

83
people/migrations/0001_initial.py

@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-09-28 18:50
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import people.controllers
import shared.utils.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.CreateModel(
name='GenericParticipationRel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('object_id', models.PositiveIntegerField()),
('order_index', models.IntegerField(default=0, verbose_name='Sortierung')),
('label', models.CharField(blank=True, max_length=2000, null=True, verbose_name='Weitere Angaben')),
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
],
options={
'verbose_name': 'Rolle/Funktion',
'verbose_name_plural': 'Rollen/Funktionen',
'ordering': ['role', 'order_index', 'person__sort_name'],
},
),
migrations.CreateModel(
name='Person',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_group', models.BooleanField(default=False, help_text='Bitte ankreuzen, wenn es sich um eine Gruppe handelt, und unten die Gruppenmitglieder auswählen', verbose_name='Gruppe')),
('_is_main_person', models.BooleanField(default=False, editable=False, verbose_name='Haupteintrag')),
('name', models.CharField(max_length=200, unique=True, verbose_name='Name')),
('slug', shared.utils.fields.AutoSlugField(help_text='Kurzfassung des Namens für die Adresszeile im Browser. Vorzugsweise englisch, keine Umlaute, nur Bindestrich als Sonderzeichen.', max_length=200, verbose_name='URL-Name')),
('sort_name', models.CharField(blank=True, max_length=200, verbose_name='Name sortierbar')),
('main_person', models.ForeignKey(blank=True, help_text='Wenn es sich um eine alternative Schreibweise oder ein Pseudonym handelt, hier den Hauptpersoneneintrag auswählen.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pseudonym_set', to='people.Person', verbose_name='Haupteintrag')),
('members', models.ManyToManyField(blank=True, related_name='groups', to='people.Person', verbose_name='Gruppenmitglieder')),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_people.person_set+', to='contenttypes.ContentType')),
],
options={
'verbose_name': 'Person',
'verbose_name_plural': 'Personen',
'ordering': ['sort_name', 'name'],
'abstract': False,
},
bases=(people.controllers.PersonController, models.Model),
),
migrations.CreateModel(
name='PersonRole',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('id_text', models.CharField(max_length=20, verbose_name='Bezeichner (intern)')),
('name_de', models.CharField(max_length=50, verbose_name='Bezeichnung (de)')),
('name_en', models.CharField(blank=True, max_length=50, null=True, verbose_name='Bezeichnung (en)')),
('label_de', models.CharField(blank=True, help_text='In der Bibliografie', max_length=200, null=True, verbose_name='Ausgabetext (de)')),
('label_en', models.CharField(blank=True, max_length=200, null=True, verbose_name='Ausgabetext (en)')),
('order_index', models.IntegerField(default=0, verbose_name='Sortierung')),
],
options={
'verbose_name': 'Funktion',
'verbose_name_plural': 'Funktionen',
'ordering': ['order_index', 'name_de', 'name_en'],
},
),
migrations.AddField(
model_name='genericparticipationrel',
name='person',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='participations', related_query_name='participations', to='people.Person', verbose_name='Person'),
),
migrations.AddField(
model_name='genericparticipationrel',
name='role',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='people.PersonRole', verbose_name='Funktion'),
),
]

0
people/migrations/__init__.py

143
people/models.py

@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# Erik Stein <code@classlibrary.net>, 2016
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from polymorphic.models import PolymorphicModel
from shared.utils.fields import AutoSlugField
from shared.utils.translation import get_translated_field
from .controllers import PersonController
@python_2_unicode_compatible
class ArtPersonMixin(models.Model):
locations = models.CharField(_("Ort(e)"), max_length=100, blank=True, null=True)
birth_year = models.PositiveIntegerField(_("Geburtsjahr"), blank=True, null=True)
# TODO birthday statt birth_year verwenden für date_hierarchy und evtl. sortierung?
year_of_death = models.PositiveIntegerField(_("Sterbejahr"), blank=True, null=True)
class Meta:
abstract = True
def __str__(self):
return "%s%s" % (self.name, self.locations and " (%s)" % self.locations or "")
class GroupMixin(models.Model):
is_group = models.BooleanField(_("Gruppe"), default=False, help_text=_("Bitte ankreuzen, wenn es sich um eine Gruppe handelt, und unten die Gruppenmitglieder auswählen"))
members = models.ManyToManyField('self', verbose_name=_("Gruppenmitglieder"), blank=True, limit_choices_to={'is_group': False}, related_name='groups', symmetrical=False)
class Meta:
abstract = True
class PseudonymMixin(models.Model):
main_person = models.ForeignKey('self', verbose_name=_("Haupteintrag"),
null=True, blank=True, on_delete=models.PROTECT,
related_name='pseudonym_set',
help_text=_("Wenn es sich um eine alternative Schreibweise oder "
"ein Pseudonym handelt, hier den Hauptpersoneneintrag auswählen."))
_is_main_person = models.BooleanField(_("Haupteintrag"), editable=False, default=False)
class Meta:
abstract = True
def save(self, *args, **kwargs):
self._is_main_person = not bool(self.main_person)
super(PseudonymMixin, self).save(*args, **kwargs)
def pseudonyms_and_self(self):
return self.get_real_instance_class().objects.filter(models.Q(pk=self.pk) | models.Q(pk__in=self.pseudonym_set.all()))
@python_2_unicode_compatible
class BasePerson(PolymorphicModel):
name = models.CharField(_("Name"), max_length=200, unique=True)
slug = AutoSlugField(_("URL-Name"), max_length=200, populate_from='name', unique_slug=True)
sort_name = models.CharField(_("Name sortierbar"), blank=True, max_length=200)
class Meta:
abstract = True
verbose_name = _("Person")
verbose_name_plural = _("Personen")
ordering = ['sort_name', 'name']
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.sort_name:
if " " in self.name:
self.sort_name = ("%s, %s" % (self.name.split()[-1], " ".join(self.name.split()[:-1])))[:20]
else:
self.sort_name = self.name[:20]
super(BasePerson, self).save(*args, **kwargs)
class Person(PersonController, PseudonymMixin, GroupMixin, BasePerson):
class Meta(BasePerson.Meta):
pass
def get_absolute_url(self):
return reverse('person-detail', kwargs={'slug': self.slug})
@python_2_unicode_compatible
class PersonRole(models.Model):
"""
Fixtures, non-deletable:
author
coauthor
translator
editor
"""
id_text = models.CharField(_("Bezeichner (intern)"), max_length=20)
name_de = models.CharField(_("Bezeichnung (de)"), max_length=50)
name_en = models.CharField(_("Bezeichnung (en)"), null=True, blank=True, max_length=50)
label_de = models.CharField(_("Ausgabetext (de)"), null=True, blank=True, max_length=200, help_text=_("In der Bibliografie"))
label_en = models.CharField(_("Ausgabetext (en)"), null=True, blank=True, max_length=200)
order_index = models.IntegerField(_("Sortierung"), default=0, blank=False, null=False)
class Meta:
verbose_name = _("Funktion")
verbose_name_plural = _("Funktionen")
ordering = ['order_index', 'name_de', 'name_en']
def __str__(self):
return self.name
@property
def label(self):
return get_translated_field(self, 'label')
@property
def name(self):
return get_translated_field(self, 'name')
@python_2_unicode_compatible
class GenericParticipationRel(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
person = models.ForeignKey(Person, verbose_name=_("Person"), related_name='participations', related_query_name='participations')
role = models.ForeignKey(PersonRole, verbose_name=_("Funktion"))
order_index = models.IntegerField(_("Sortierung"), default=0, blank=False, null=False)
label = models.CharField(_("Weitere Angaben"), null=True, blank=True, max_length=2000)
# TODO Add label_en
class Meta:
verbose_name = _("Rolle/Funktion")
verbose_name_plural = _("Rollen/Funktionen")
ordering = ['role', 'order_index', 'person__sort_name']
def __str__(self):
return _("%s als %s bei „%s") % (self.person, self.role, self.content_object)

27
people/templates/people/person_list.html

@ -0,0 +1,27 @@
{% extends "base_site.html" %}
{% block main %}
<nav class="alphabet">
{% for letter in alphabet %}
<a href="{% url 'person-list-letter' letter=letter %}">{{ letter|upper }}</a>
{% endfor %}
</nav>
<section class="alphabetical-list">
{% for p in object_list %}
{% ifchanged p.sort_name.0|slugify %}
{% if not forloop.first %}</ul>{% endif %}
<h3>{{ p.sort_name.0|slugify|upper }}</h3>
<ul>
{% endifchanged %}
<li id="{{ p.slug }}">
<a href="{{ p.get_absolute_url }}">{{ p.name }}{% if p.main_person %} [= {{ p.main_person.name }}]{% endif %}</a>
</li>
{% if forloop.last %}
</ul>
{% endif %}
{% endfor %}
</section>
{% endblock main %}

3
people/tests.py

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

13
people/urls.py

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# Erik Stein <code@classlibrary.net>, 2017
from django.conf.urls import url
from .views import PersonListView
urlpatterns = [
url(r'^$', PersonListView.as_view(), name='person-list'),
url(r'^(?P<letter>\w)/$', PersonListView.as_view(), name='person-list-letter'),
]

33
people/views.py

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# Erik Stein <code@classlibrary.net>, 2017
from django.http import HttpResponsePermanentRedirect
from django.core.urlresolvers import reverse
from django.views.generic import ListView
from .models import Person
class PersonListView(ListView):
model = Person
template_name = 'person/person_list.html'
def get(self, request, *args, **kwargs):
if 'letter' not in kwargs:
return HttpResponsePermanentRedirect(reverse('person-list-letter', kwargs={'letter': 'a'}))
else:
return super(PersonListView, self).get(request, *args, **kwargs)
def get_queryset(self):
qs = super(PersonListView, self).get_queryset()
letter = self.kwargs.get('letter', 'a')
return qs.filter(sort_name__istartswith=letter)
def get_context_data(self, **kwargs):
context = super(PersonListView, self).get_context_data(**kwargs)
context['selected_letter'] = 'a'
context['alphabet'] = 'abcdefghijklmnopqrstuvwxyz'
return context

35
requirements.txt

@ -0,0 +1,35 @@
django<1.11
bs4
gunicorn
ipdb
markdown
pilkit
pillow
psycopg2
pytz
six
translitcodec
unicodecsv
# unicodedata2 is a backport for python2
unicodedata2
django-admin-sortable2
django-admin-steroids
django-content-editor
django-extensions
# django-imagekit
# django-imperavi
django-markdown
#django-markup
django-model-utils
django-mptt
django-polymorphic
django-reversion
# solid_i18n
django-solo
django-sortedm2m
django-stronghold
-e git+gogs@projects.c--y.net:erik/django-shared-utils.git#egg=utils
-e git+gogs@projects.c--y.net:erik/django-shared-multilingual.git#egg=django_shared_multilingual

56
setup.py

@ -0,0 +1,56 @@
#!/usr/bin/env python
import os
from io import open
from setuptools import find_packages, setup
def read(filename):
path = os.path.join(os.path.dirname(__file__), filename)
with open(path, encoding='utf-8') as handle:
return handle.read()
setup(
name='django-people',
version=__import__('people').__version__,
description='Person model, mixins and helpers for Django',
long_description=read('README'),
author='Erik Stein',
author_email='erik@classlibrary.net',
# url='https://github.com/sha-red/django-people/',
license='BSD License',
platforms=['OS Independent'],
packages=find_packages(
exclude=['tests', 'testapp']
),
include_package_data=True,
install_requires=[
# 'Django>=1.9', commented out to make `pip install -U` easier
'django-admin-steroids',
'django-polymorphic',
'git+https://projects.c--y.net/erik/django-shared-utils.git#egg=shared-utils'
],
extras_require={
'all': [
],
},
classifiers=[
# 'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Software Development',
],
zip_safe=False,
)
Loading…
Cancel
Save