commit
61e0031848
18 changed files with 565 additions and 0 deletions
@ -0,0 +1,10 @@ |
|||||||
|
*.py? |
||||||
|
*.sw? |
||||||
|
*~ |
||||||
|
.coverage |
||||||
|
.tox |
||||||
|
/*.egg-info |
||||||
|
/MANIFEST |
||||||
|
build |
||||||
|
dist |
||||||
|
htmlcov |
@ -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. |
@ -0,0 +1,4 @@ |
|||||||
|
include LICENSE |
||||||
|
include MANIFEST.in |
||||||
|
include README |
||||||
|
recursive-include people/templates * |
@ -0,0 +1,3 @@ |
|||||||
|
# django-people |
||||||
|
|
||||||
|
Models for person and several mixins, group and pseudonym support. |
@ -0,0 +1,4 @@ |
|||||||
|
VERSION = (1, 0, 0) |
||||||
|
__version__ = '.'.join(map(str, VERSION)) |
||||||
|
|
||||||
|
default_app_config = 'people.apps.PeopleConfig' |
@ -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 |
@ -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") |
@ -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 |
@ -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,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) |
@ -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 %} |
@ -0,0 +1,3 @@ |
|||||||
|
from django.test import TestCase |
||||||
|
|
||||||
|
# Create your tests here. |
@ -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'), |
||||||
|
] |
@ -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 |
||||||
|
|
@ -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 |
@ -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…
Reference in new issue