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