Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add automated tests and adapt for newer versions of Django #57

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
**/*.pyc
**/.project
**/.pydevproject
**/dist
**/*~
**/MANIFEST
**/*egg*
**/*.bak
**/*.tmproj

**/Dockerfile
**/.sh
**/.md
1 change: 1 addition & 0 deletions LICENCE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Copyright (c) 2009, Weston Nielson ([email protected])
2010, Jan Schrewe ([email protected])
2017, Arthur Hanson ([email protected])
2024, Ipamo ([email protected])

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
22 changes: 19 additions & 3 deletions genericadmin/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import json
from functools import update_wrapper

from django import VERSION
from django.contrib import admin
from django.conf.urls import url

try:
from django.conf.urls import url
except ImportError:
from django.urls import re_path as url # Django >= 4.0

from django.conf import settings
try:
from django.contrib.contenttypes.generic import GenericForeignKey, GenericTabularInline, GenericStackedInline
Expand All @@ -14,7 +20,10 @@
try:
from django.utils.encoding import force_text
except ImportError:
from django.utils.encoding import force_unicode as force_text
try:
from django.utils.encoding import force_unicode as force_text
except ImportError:
from django.utils.encoding import force_str as force_text # Django >= 4.0
from django.utils.text import capfirst
from django.contrib.admin.widgets import url_params_from_lookup_dict
from django.http import HttpResponse, HttpResponseNotAllowed, Http404
Expand All @@ -40,6 +49,8 @@ def __init__(self, model, admin_site):
media = list(self.Media.js)
except:
media = []
if VERSION >= (2,2):
media.append('admin/js/jquery.init.js') # Django >= 2.2
media.append(JS_PATH + 'genericadmin.js')
self.Media.js = tuple(media)

Expand All @@ -63,7 +74,12 @@ def get_generic_field_list(self, request, prefix=''):
fields['prefix'] = prefix
field_list.append(fields)
else:
for field in self.model._meta.virtual_fields:
try:
virtual_fields = self.model._meta.virtual_fields
except AttributeError:
virtual_fields = self.model._meta.private_fields # Django >= 2.0

for field in virtual_fields:
if isinstance(field, GenericForeignKey) and \
field.ct_field not in exclude and field.fk_field not in exclude:
field_list.append({
Expand Down
29 changes: 26 additions & 3 deletions genericadmin/static/genericadmin/js/genericadmin.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,32 @@

updated by Jonathan Ellenberger ([email protected])

updated by Ipamo ([email protected])

*/
(function($) {
function prefixWithObjRoot(url) {
// prefix the given URL with the necessary reverse paths to reach object root ('changelist' admin page)
if (window.location.pathname.endsWith('/change/') || window.location.pathname.endsWith('/change')) { // Django >= 1.9
return `../../${url}`;
}
else {
return `../${url}`;
}
}

if (! id_to_windowname) { // Django >= 3.1
function id_to_windowname(text) {
text = text.replace(/\./g, '__dot__');
text = text.replace(/\-/g, '__dash__');
return text;
}
}

var GenericAdmin = {
url_array: null,
fields: null,
obj_url: "../obj-data/",
obj_url: prefixWithObjRoot('obj-data/'),
admin_media_url: window.__admin_media_prefix__,
popup: '_popup',

Expand Down Expand Up @@ -73,7 +93,7 @@
},

getLookupUrl: function(cID) {
return '../../../' + this.url_array[cID][0] + '/' + this.getLookupUrlParams(cID);
return prefixWithObjRoot('../../' + this.url_array[cID][0] + '/' + this.getLookupUrlParams(cID));
},

getFkId: function() {
Expand Down Expand Up @@ -308,7 +328,7 @@

$(document).ready(function() {
$.ajax({
url: '../genericadmin-init/',
url: prefixWithObjRoot('genericadmin-init/'),
dataType: 'json',
success: function(data) {
var url_array = data.url_array,
Expand All @@ -324,6 +344,9 @@
$.extend({}, InlineAdmin).install(fields, url_array, popup_var);
}
}
},
error: function(res, err) {
console.error(`cannot fetch genericadmin-init: ${err} - ${res.status} ${res.statusText}`);
}
});
});
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ def convert_readme():
name='django-genericadmin',
version='0.7.0',
description="Adds support for generic relations within Django's admin interface.",
author='Weston Nielson, Jan Schrewe, Arthur Hanson',
author_email='[email protected], [email protected], [email protected]',
author='Weston Nielson, Jan Schrewe, Arthur Hanson, Ipamo',
author_email='[email protected], [email protected], [email protected], [email protected]',
url='https://github.com/arthanson/django-genericadmin',
packages = ['genericadmin'],
# package_data={'genericadmin': ['static/genericadmin/js/genericadmin.js']},
Expand Down
14 changes: 14 additions & 0 deletions tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
ARG PYTHON_VERSION
FROM python:${PYTHON_VERSION}

ARG DJANGO_VERSION
RUN pip install django==${DJANGO_VERSION}

WORKDIR /app
COPY genericadmin ./genericadmin
COPY tests ./tests
RUN python tests/manage.py migrate &&\
python tests/manage.py prepare

EXPOSE 8000
CMD ["python", "tests/manage.py", "runserver", "0.0.0.0:8000"]
Empty file added tests/__init__.py
Empty file.
Empty file added tests/demo/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions tests/demo/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from django.contrib.admin import register
from genericadmin.admin import GenericAdminModelAdmin
from .models import TaggedItem

@register(TaggedItem)
class TaggedItemAdmin(GenericAdminModelAdmin):
list_display = ['tag', 'content_object']
Empty file.
Empty file.
41 changes: 41 additions & 0 deletions tests/demo/management/commands/prepare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.management import BaseCommand
from ...models import TaggedItem

User = get_user_model()

class Command(BaseCommand):
def handle(self, **options):
self._seed_admin_user()
self._seed_other_user()
self._seed_tagged_item()

def _seed_admin_user(self):
try:
user = User.objects.get(username='admin')
except User.DoesNotExist:
user = User(username='admin', email='[email protected]')

if not user.is_staff:
user.is_staff = True

if not user.is_superuser:
user.is_superuser = True

if not user.password:
user.set_password('admin')

user.save()

def _seed_other_user(self):
try:
user = User.objects.get(username='other')
except User.DoesNotExist:
user = User(username='other', email='[email protected]')

user.save()

def _seed_tagged_item(self):
user = User.objects.get(username='admin')
TaggedItem.objects.get_or_create(content_type=ContentType.objects.get_for_model(User), object_id=user.id, tag='admin-tag')
30 changes: 30 additions & 0 deletions tests/demo/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

dependencies = [
('contenttypes', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='TaggedItem',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('tag', models.SlugField()),
('object_id', models.PositiveIntegerField()),
('content_type', models.ForeignKey(to='contenttypes.ContentType',on_delete=models.CASCADE)),
],
options={
},
bases=(models.Model,),
),
migrations.AlterIndexTogether(
name='taggeditem',
index_together=set([('content_type', 'object_id')]),
),
]
Empty file.
18 changes: 18 additions & 0 deletions tests/demo/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models


class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey("content_type", "object_id")

def __str__(self):
return self.tag

class Meta:
index_together = [
("content_type", "object_id"),
]
129 changes: 129 additions & 0 deletions tests/demo/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""
Django settings for demo project.

Generated by 'django-admin startproject' using Django 1.11.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""
from django import VERSION
import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 't8no3y00_n&0lxz9#1!0$knft$c%3t7@s5j-ix4b_63if7f7!y'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'genericadmin',
'demo',
]

MIDDLEWARE = []

if VERSION >= (1, 8):
MIDDLEWARE.append('django.middleware.security.SecurityMiddleware')

MIDDLEWARE += [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

if VERSION < (1, 10):
MIDDLEWARE_CLASSES = MIDDLEWARE

ROOT_URLCONF = 'tests.demo.urls'

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

WSGI_APPLICATION = 'tests.demo.wsgi.application'


# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}


# Password validation
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_URL = '/static/'
Loading