Skip to content

Commit

Permalink
[admin] Removed logic duplicates openwisp#40
Browse files Browse the repository at this point in the history
Removed the logics which were duplicated
in the others modules and redefined it
here to enable reusability and ease debugging.

Fixes openwisp#40
  • Loading branch information
NoumbissiValere committed Apr 24, 2019
1 parent eb1e0e0 commit 1a5ac0a
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 4 deletions.
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Current features
* **Customized admin theme** for OpenWISP modules
* **TimeStamped** models and mixins which add self-updating ``created`` and ``modified`` fields.
* **UUIDModel**: base model with a UUID4 primary key
* **UUIDAdmin**: base admin which defines a uuid field from a UUID primary key
* **get_random_key**: generates an object key of 32 characters
* **DependencyLoader**: template loader which looks in the templates dir of all django-apps
listed in ``EXTENDED_APPS``
* **DependencyFinder**: finds static files of django-apps listed in ``EXTENDED_APPS``
Expand Down
33 changes: 33 additions & 0 deletions openwisp_utils/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django.contrib.admin import ModelAdmin
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _


class TimeReadonlyAdminMixin(object):
Expand Down Expand Up @@ -60,3 +62,34 @@ def has_changed(self):
if self.instance._state.adding:
return True
return super(AlwaysHasChangedMixin, self).has_changed()


class UUIDAdmin(object):
"""
Defines a field name uuid whose value is that
of the id of the object
"""
def uuid(self, obj):
return obj.pk

uuid.short_description = _('UUID')


class GetUrlAdmin(object):
"""
Return a receive_url field whose value is that of
a view_name concatenated with the obj id and/or
with the key of the obj
"""
def receive_url(self, obj, view_name, key=False):
"""
:param view_name: The name of the view usually an api
:param key: determines if the key should be added or not
"""
url = reverse('{0}'.format(view_name), kwargs={'pk': obj.pk})
if key:
return '{0}?key={1}'.format(url, obj.key)
else:
return url

receive_url.short_description = _('Url')
10 changes: 10 additions & 0 deletions openwisp_utils/static/openwisp-utils/js/receive_url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
django.jQuery(function ($) {
"use strict";
var p = $('.field-receive_url p, .field-receive_url > div > div'),
value = window.location.origin + p.text();
p.html('<input readonly id="id_receive_url" type="text" class="vTextField readonly" value="' + value + '">');
var field = $('#id_receive_url');
field.click(function () {
$(this).select();
});
};
10 changes: 10 additions & 0 deletions openwisp_utils/static/openwisp-utils/js/uuid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
django.jQuery(function ($) {
"use strict";
var container = $('.field-uuid .readonly').eq(0),
value = container.text();
container.html('<input readonly id="id_id" type="text" class="vTextField readonly" value="' + value + '">');
var id = $('#id_id');
id.click(function () {
$(this).select();
});
});
8 changes: 8 additions & 0 deletions openwisp_utils/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.utils.crypto import get_random_string


def get_random_key():
"""
generates a device key of 32 characters
"""
return get_random_string(length=32)
8 changes: 8 additions & 0 deletions openwisp_utils/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.core.validators import RegexValidator, _lazy_re_compile
from django.utils.translation import ugettext_lazy as _

key_validator = RegexValidator(
_lazy_re_compile('^[^\s/\.]+$'),
message=_('Key must not contain spaces, dots or slashes.'),
code='invalid',
)
1 change: 1 addition & 0 deletions tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'openwisp_utils.admin_theme',
'openwisp_utils',
# all-auth
'django.contrib.sites',
'allauth',
Expand Down
28 changes: 26 additions & 2 deletions tests/test_project/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.contrib import admin
from django.forms import ModelForm
from openwisp_utils.admin import AlwaysHasChangedMixin, ReadOnlyAdmin
from openwisp_utils.admin import (AlwaysHasChangedMixin, ReadOnlyAdmin,
UUIDAdmin)

from .models import Operator, Project, RadiusAccounting

Expand All @@ -24,9 +25,32 @@ class OperatorInline(admin.StackedInline):
extra = 0


class ProjectAdmin(admin.ModelAdmin):
class ProjectAdmin(admin.ModelAdmin, UUIDAdmin):
inlines = [OperatorInline]
list_display = ['name']
readonly_fields = ['uuid']
fields = ['name', 'key', 'uuid']

class Media:
js = ('openwisp-utils/js/uuid.js',)

def _get_fields(self, fields, request, obj=None):
"""
removes readonly_fields in add view
"""
if obj:
return fields
new_fields = fields[:]
for field in self.readonly_fields:
if field in new_fields:
new_fields.remove(field)
return new_fields

def get_fields(self, request, obj=None):
return self._get_fields(self.fields, request, obj)

def get_readonly_fields(self, request, obj=None):
return self._get_fields(self.readonly_fields, request, obj)


admin.site.register(Operator, OperatorAdmin)
Expand Down
27 changes: 27 additions & 0 deletions tests/test_project/migrations/0004_add_key_id_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 2.0.13 on 2019-04-21 19:50

import django.core.validators
from django.db import migrations, models
import openwisp_utils.utils
import re
import uuid


class Migration(migrations.Migration):

dependencies = [
('test_project', '0003_project_operator'),
]

operations = [
migrations.AddField(
model_name='project',
name='key',
field=models.CharField(db_index=True, default=openwisp_utils.utils.get_random_key, help_text='unique device key', max_length=64, unique=True, validators=[django.core.validators.RegexValidator(re.compile('^[^\\s/\\.]+$'), code='invalid', message='Key must not contain spaces, dots or slashes.')]),
),
migrations.AlterField(
model_name='project',
name='id',
field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False),
),
]
12 changes: 10 additions & 2 deletions tests/test_project/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from openwisp_users.mixins import OrgMixin
from openwisp_utils.base import TimeStampedEditableModel
from openwisp_utils.base import TimeStampedEditableModel, UUIDModel
from openwisp_utils.utils import get_random_key
from openwisp_utils.validators import key_validator


class Shelf(OrgMixin, TimeStampedEditableModel):
Expand Down Expand Up @@ -45,10 +47,16 @@ class RadiusAccounting(models.Model):
blank=True)


class Project(models.Model):
class Project(UUIDModel):
name = models.CharField(max_length=64,
null=True,
blank=True)
key = models.CharField(max_length=64,
unique=True,
db_index=True,
default=get_random_key,
validators=[key_validator],
help_text=_('unique device key'))

def __str__(self):
return self.name
Expand Down
9 changes: 9 additions & 0 deletions tests/test_project/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@


class TestAdmin(TestCase, CreateMixin):
TEST_KEY = 'w1gwJxKaHcamUw62TQIPgYchwLKn3AA0'
accounting_model = RadiusAccounting

def setUp(self):
Expand Down Expand Up @@ -36,6 +37,7 @@ def test_alwayshaschangedmixin(self):
self.assertEqual(Operator.objects.count(), 0)
params = {
'name': 'test',
'key': self.TEST_KEY,
'operator_set-TOTAL_FORMS': 1,
'operator_set-INITIAL_FORMS': 0,
'operator_set-MIN_NUM_FORMS': 0,
Expand All @@ -53,3 +55,10 @@ def test_alwayshaschangedmixin(self):
self.assertEqual(Operator.objects.count(), 1)
project = Project.objects.first()
self.assertEqual(project.name, params['name'])

def test_uuid_field_in_change(self):
p = Project.objects.create(name='test-project')
path = reverse('admin:test_project_project_change', args=[p.pk])
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'field-uuid')
22 changes: 22 additions & 0 deletions tests/test_project/tests/test_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.core.exceptions import ValidationError
from django.test import TestCase

from ..models import Project


class TestModel(TestCase):
TEST_KEY = 'w1gwJxKaHcamUw62TQIPgYchwLKn3AA0'

def test_key_validator(self):
p = Project.objects.create(name='test_project')
p.key = 'key/key'
with self.assertRaises(ValidationError):
p.full_clean()
p.key = 'key.key'
with self.assertRaises(ValidationError):
p.full_clean()
p.key = 'key key'
with self.assertRaises(ValidationError):
p.full_clean()
p.key = self.TEST_KEY
p.full_clean()

0 comments on commit 1a5ac0a

Please sign in to comment.