From e7fbb42699c7b80f24fcb34a93f44480e5903798 Mon Sep 17 00:00:00 2001 From: TheOneAboveAllTitan Date: Wed, 15 Jan 2020 02:31:14 +0530 Subject: [PATCH 1/2] [docs] Updated AbstractModels page --- docs/source/general/abstract_models.rst | 237 +++++++++++++----------- 1 file changed, 130 insertions(+), 107 deletions(-) diff --git a/docs/source/general/abstract_models.rst b/docs/source/general/abstract_models.rst index 9d8d9e1..203eda9 100644 --- a/docs/source/general/abstract_models.rst +++ b/docs/source/general/abstract_models.rst @@ -2,20 +2,22 @@ Abstract Models =============== -Firstly, we need to add basic models. TimeStampedEditableModel is an abstract base class model that provides self-updating -created and modified fields. If we write the base class and put abstract=True in the Meta class, this model will then not be used to -create any database table. Instead, when it is used as a base class for other models, its fields will be added to those -of the child class. +Firstly, we need to add basic models. TimeStampedEditableModel is an abstract base class model, imported from openwisp_utils that provides self-updating +created and modified fields. + +.. note:: + An abstract class model is not used to create any database table, but is used as a base class for other models. + Its fields will be added to those of the child class. Assigning ``abstract=True`` in Meta class, makes that class abstract. Example of TimeStampedEditableModel code: .. code-block:: python - #django_freeradius/base/models.py + #openwisp_utils/openwisp_utils/base.py - from model_utils.fields import AutoCreatedField, AutoLastModifiedField + from model_utils.fields import AutoCreatedField, AutoLastModifiedField - class TimeStampedEditableModel(models.Model): + class TimeStampedEditableModel(models.Model): """ An abstract base class model that provides self-updating ``created`` and ``modified`` fields. @@ -33,7 +35,10 @@ Example: .. code-block:: python - #django_freeradius/base/models.py + #django_freeradius/base/models.py + + from openwisp_utils.base import TimeStampedEditableModel + class BaseModel(TimeStampedEditableModel): id = None @@ -42,25 +47,34 @@ Example: abstract = True - class AbstractRadiusReply(BaseModel): + class AbstractRadiusReply(AutoUsernameMixin, BaseModel): username = models.CharField(verbose_name=_('username'), max_length=64, - db_index=True) + db_index=True, + # blank values are forbidden with custom validation + # because this field can left blank if the user + # foreign key is filled (it will be auto-filled) + blank=True) value = models.CharField(verbose_name=_('value'), max_length=253) op = models.CharField(verbose_name=_('operator'), - max_length=2, - choices=RADOP_REPLY_TYPES, - default='=') + max_length=2, + choices=RADOP_REPLY_TYPES, + default='=') attribute = models.CharField(verbose_name=_('attribute'), max_length=64) + # the foreign key is not part of the standard freeradius schema + user = models.ForeignKey(settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + blank=True, + null=True) class Meta: db_table = 'radreply' - verbose_name = _('radius reply') - verbose_name_plural = _('radius replies') + verbose_name = _('reply') + verbose_name_plural = _('replies') abstract = True def __str__(self): - return self.username + return self.username Introduce a ModelAdmin for TimeStampedEditableAdmin @@ -70,25 +84,36 @@ Example of code: .. code-block:: python + #django_freeradius/base/admin.py - #django_freeradius/base/admin.py + from django.contrib.admin import ModelAdmin + from openwisp_utils.admin import TimeReadonlyAdminMixin - from django.contrib.admin import ModelAdmin - from openwisp_utils.admin import TimeReadonlyAdminMixin - - class TimeStampedEditableAdmin(TimeReadonlyAdminMixin, ModelAdmin): + + class TimeStampedEditableAdmin(TimeReadonlyAdminMixin, ModelAdmin): pass - class AbstractRadiusReplyAdmin(TimeStampedEditableAdmin): - pass + class AbstractRadiusReplyAdmin(TimeStampedEditableAdmin): + list_display = ['username', 'attribute', 'op', + 'value', 'created', 'modified'] + autocomplete_fields = ['user'] + form = ModeSwitcherForm + fields = ['mode', + 'user', + 'username', + 'attribute', + 'op', + 'value', + 'created', + 'modified'] Creating a Reusable App ----------------------- -First, You have to install `swapper`. If you are publishing your reusable app as a Python package, -be sure to add `swapper` to your project's dependencies.You may also want to take a look at the `Swapper Guide +Install `swapper` to begin with this. If your reusable app is being published as a Python package, +be sure to add `swapper` to your project's dependencies. Learn more about `swapper` at the `Swapper Guide ` Install swapper: @@ -102,14 +127,15 @@ In your reusable models, use ``import swapper`` and add to Meta class ``swapp .. code-block:: python - #django_freeradius/models.py + #django_freeradius/models.py - import swapper + from swapper import swappable_setting - from .base.models import (AbstractNas, AbstractRadiusAccounting, - AbstractRadiusCheck, AbstractRadiusGroupCheck, - AbstractRadiusGroupReply, AbstractRadiusPostAuth, - AbstractRadiusReply, AbstractRadiusUserGroup) + from .base.models import ( + AbstractNas, AbstractRadiusAccounting, AbstractRadiusBatch, AbstractRadiusCheck, AbstractRadiusGroup, + AbstractRadiusGroupCheck, AbstractRadiusGroupReply, AbstractRadiusPostAuth, AbstractRadiusReply, + AbstractRadiusToken, AbstractRadiusUserGroup, + ) class RadiusCheck(AbstractRadiusCheck): @@ -127,43 +153,44 @@ and make the following changes: .. code-block:: python - #django_freeradius/migrations - - import swapper - - class Migration(migrations.Migration): - - initial = True - - dependencies = [ - swapper.dependency('django_freeradius', 'RadiusReply'), - swapper.dependency('django_freeradius', 'RadiusCheck'), - ] - - operations = [ - migrations.CreateModel( - name='Nas', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('nas_name', models.CharField(db_column='nasname', db_index=True, help_text='NAS Name (or IP address)', max_length=128, unique=True, verbose_name='nas name')), - ('short_name', models.CharField(db_column='shortname', max_length=32, verbose_name='short name')), - ('type', models.CharField(max_length=30, verbose_name='type')), - ('secret', models.CharField(help_text='Shared Secret', max_length=60, verbose_name='secret')), - ('ports', models.IntegerField(blank=True, null=True, verbose_name='ports')), - ('community', models.CharField(blank=True, max_length=50, null=True, verbose_name='community')), - ('description', models.CharField(max_length=200, null=True, verbose_name='description')), - ('server', models.CharField(max_length=64, null=True, verbose_name='server')), - ], - options={ - 'db_table': 'nas', - 'swappable': swapper.swappable_setting('django_freeradius', 'Nas'), - 'verbose_name': 'nas', - 'abstract': False, - 'verbose_name_plural': 'nas', - }, - ), + #django_freeradius/migrations + + import swapper + + + class Migration(migrations.Migration): + + initial = True + + dependencies = [ + swapper.dependency('django_freeradius', 'RadiusReply'), + swapper.dependency('django_freeradius', 'RadiusCheck'), + ] + + operations = [ + migrations.CreateModel( + name='Nas', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('nas_name', models.CharField(db_column='nasname', db_index=True, help_text='NAS Name (or IP address)', max_length=128, unique=True, verbose_name='nas name')), + ('short_name', models.CharField(db_column='shortname', max_length=32, verbose_name='short name')), + ('type', models.CharField(max_length=30, verbose_name='type')), + ('secret', models.CharField(help_text='Shared Secret', max_length=60, verbose_name='secret')), + ('ports', models.IntegerField(blank=True, null=True, verbose_name='ports')), + ('community', models.CharField(blank=True, max_length=50, null=True, verbose_name='community')), + ('description', models.CharField(max_length=200, null=True, verbose_name='description')), + ('server', models.CharField(max_length=64, null=True, verbose_name='server')), + ], + options={ + 'db_table': 'nas', + 'swappable': swapper.swappable_setting('django_freeradius', 'Nas'), + 'verbose_name': 'nas', + 'abstract': False, + 'verbose_name_plural': 'nas', + }, + ), Extends Models -------------- @@ -174,20 +201,20 @@ Example: .. code-block:: python - #sample_radius/models.py + #sample_radius/models.py - from django.db import models - from django.utils.translation import ugettext_lazy as _ + from django.db import models + from django.utils.translation import ugettext_lazy as _ - from django_freeradius.models import (AbstractNas, AbstractRadiusAccounting, + from django_freeradius.models import (AbstractNas, AbstractRadiusAccounting, AbstractRadiusCheck, AbstractRadiusGroupCheck, AbstractRadiusGroupReply, AbstractRadiusPostAuth, AbstractRadiusReply, AbstractRadiusUserGroup) - class RadiusCheck(AbstractRadiusCheck): - details = models.CharField( + class RadiusCheck(AbstractRadiusCheck): + details = models.CharField( verbose_name=_('details'), max_length=64, blank=True, null=True) @@ -195,31 +222,27 @@ Add swapper.load_model() to sample_radius/admin.py. Example: .. code-block:: python - from django.contrib import admin + #sample_radius/admin.py - import swapper - from django_freeradius.admin import (AbstractNasAdmin, - AbstractRadiusAccountingAdmin, - AbstractRadiusCheckAdmin, - AbstractRadiusGroupCheckAdmin, - AbstractRadiusGroupReplyAdmin, - AbstractRadiusPostAuthAdmin, - AbstractRadiusReplyAdmin, - AbstractRadiusUserGroupAdmin) + from django.contrib import admin - RadiusGroupReply = swapper.load_model("django_freeradius", "RadiusGroupReply") - RadiusGroupCheck = swapper.load_model("django_freeradius", "RadiusGroupCheck") - RadiusUserGroup = swapper.load_model("django_freeradius", "RadiusUserGroup") - RadiusReply = swapper.load_model("django_freeradius", "RadiusReply") - RadiusCheck = swapper.load_model("django_freeradius", "RadiusCheck") - RadiusPostAuth = swapper.load_model("django_freeradius", "RadiusPostAuth") - Nas = swapper.load_model("django_freeradius", "Nas") - RadiusAccounting = swapper.load_model("django_freeradius", "RadiusAccounting") + import swapper + from django_freeradius.admin import (AbstractRadiusGroupCheckAdmin, + AbstractRadiusGroupReplyAdmin, + ) + RadiusGroupReply = swapper.load_model("django_freeradius", "RadiusGroupReply") + RadiusGroupCheck = swapper.load_model("django_freeradius", "RadiusGroupCheck") - @admin.register(RadiusCheck) - class RadiusCheckAdmin(AbstractRadiusCheckAdmin): - pass + + @admin.register(RadiusCheck) + class RadiusGroupCheckAdmin(AbstractRadiusGroupCheckAdmin): + pass + + + @admin.register(RadiusCheck) + class RadiusGroupReplyAdmin(AbstractRadiusGroupReplyAdmin): + pass --------------- @@ -230,15 +253,15 @@ Update the settings to trigger the swapper: .. code-block:: python - #django_freeradius/tests/settings.py - - if os.environ.get('SAMPLE_APP', False): - INSTALLED_APPS.append('sample_radius') - DJANGO_FREERADIUS_RADIUSREPLY_MODEL = "sample_radius.RadiusReply" - DJANGO_FREERADIUS_RADIUSGROUPREPLY_MODEL = "sample_radius.RadiusGroupReply" - DJANGO_FREERADIUS_RADIUSCHECK_MODEL = "sample_radius.RadiusCheck" - DJANGO_FREERADIUS_RADIUSGROUPCHECK_MODEL = "sample_radius.RadiusGroupCheck" - DJANGO_FREERADIUS_RADIUSACCOUNTING_MODEL = "sample_radius.RadiusAccounting" - DJANGO_FREERADIUS_NAS_MODEL = "sample_radius.Nas" - DJANGO_FREERADIUS_RADIUSUSERGROUP_MODEL = "sample_radius.RadiusUserGroup" - DJANGO_FREERADIUS_RADIUSPOSTAUTHENTICATION_MODEL = "sample_radius.RadiusPostAuth" + #django_freeradius/tests/settings.py + + if os.environ.get('SAMPLE_APP', False): + INSTALLED_APPS.append('sample_radius') + DJANGO_FREERADIUS_RADIUSREPLY_MODEL = "sample_radius.RadiusReply" + DJANGO_FREERADIUS_RADIUSGROUPREPLY_MODEL = "sample_radius.RadiusGroupReply" + DJANGO_FREERADIUS_RADIUSCHECK_MODEL = "sample_radius.RadiusCheck" + DJANGO_FREERADIUS_RADIUSGROUPCHECK_MODEL = "sample_radius.RadiusGroupCheck" + DJANGO_FREERADIUS_RADIUSACCOUNTING_MODEL = "sample_radius.RadiusAccounting" + DJANGO_FREERADIUS_NAS_MODEL = "sample_radius.Nas" + DJANGO_FREERADIUS_RADIUSUSERGROUP_MODEL = "sample_radius.RadiusUserGroup" + DJANGO_FREERADIUS_RADIUSPOSTAUTHENTICATION_MODEL = "sample_radius.RadiusPostAuth" From d59828b41fcf7c018f58f952b88f5ae0dedf86be Mon Sep 17 00:00:00 2001 From: TheOneAboveAllTitan Date: Mon, 20 Jan 2020 21:52:47 +0530 Subject: [PATCH 2/2] [docs] Extending models and admin WIP, extending models and admin has been documented. --- docs/source/general/abstract_models.rst | 267 ------------------ .../general/extend_django_freeradius.rst | 148 ++++++++++ docs/source/index.rst | 2 +- 3 files changed, 149 insertions(+), 268 deletions(-) delete mode 100644 docs/source/general/abstract_models.rst create mode 100644 docs/source/general/extend_django_freeradius.rst diff --git a/docs/source/general/abstract_models.rst b/docs/source/general/abstract_models.rst deleted file mode 100644 index 203eda9..0000000 --- a/docs/source/general/abstract_models.rst +++ /dev/null @@ -1,267 +0,0 @@ -=============== -Abstract Models -=============== - -Firstly, we need to add basic models. TimeStampedEditableModel is an abstract base class model, imported from openwisp_utils that provides self-updating -created and modified fields. - -.. note:: - An abstract class model is not used to create any database table, but is used as a base class for other models. - Its fields will be added to those of the child class. Assigning ``abstract=True`` in Meta class, makes that class abstract. - -Example of TimeStampedEditableModel code: - -.. code-block:: python - - #openwisp_utils/openwisp_utils/base.py - - from model_utils.fields import AutoCreatedField, AutoLastModifiedField - - class TimeStampedEditableModel(models.Model): - """ - An abstract base class model that provides self-updating - ``created`` and ``modified`` fields. - """ - created = AutoCreatedField(_('created'), editable=True) - modified = AutoLastModifiedField(_('modified'), editable=True) - - class Meta: - abstract = True - - -Include the TimeStampedEditableModel to the AbstractModel ---------------------------------------------------------- -Example: - -.. code-block:: python - - #django_freeradius/base/models.py - - from openwisp_utils.base import TimeStampedEditableModel - - - class BaseModel(TimeStampedEditableModel): - id = None - - class Meta: - abstract = True - - - class AbstractRadiusReply(AutoUsernameMixin, BaseModel): - username = models.CharField(verbose_name=_('username'), - max_length=64, - db_index=True, - # blank values are forbidden with custom validation - # because this field can left blank if the user - # foreign key is filled (it will be auto-filled) - blank=True) - value = models.CharField(verbose_name=_('value'), max_length=253) - op = models.CharField(verbose_name=_('operator'), - max_length=2, - choices=RADOP_REPLY_TYPES, - default='=') - attribute = models.CharField(verbose_name=_('attribute'), max_length=64) - # the foreign key is not part of the standard freeradius schema - user = models.ForeignKey(settings.AUTH_USER_MODEL, - on_delete=models.CASCADE, - blank=True, - null=True) - - class Meta: - db_table = 'radreply' - verbose_name = _('reply') - verbose_name_plural = _('replies') - abstract = True - - def __str__(self): - return self.username - - -Introduce a ModelAdmin for TimeStampedEditableAdmin ---------------------------------------------------- - -Example of code: - -.. code-block:: python - - #django_freeradius/base/admin.py - - from django.contrib.admin import ModelAdmin - from openwisp_utils.admin import TimeReadonlyAdminMixin - - - class TimeStampedEditableAdmin(TimeReadonlyAdminMixin, ModelAdmin): - pass - - - class AbstractRadiusReplyAdmin(TimeStampedEditableAdmin): - list_display = ['username', 'attribute', 'op', - 'value', 'created', 'modified'] - autocomplete_fields = ['user'] - form = ModeSwitcherForm - fields = ['mode', - 'user', - 'username', - 'attribute', - 'op', - 'value', - 'created', - 'modified'] - - -Creating a Reusable App ------------------------ - -Install `swapper` to begin with this. If your reusable app is being published as a Python package, -be sure to add `swapper` to your project's dependencies. Learn more about `swapper` at the `Swapper Guide -` - -Install swapper: - -.. code-block:: shell - - pip install swapper - - -In your reusable models, use ``import swapper`` and add to Meta class ``swappable = swapper.swappable_setting('reusable_app', 'model')``: - -.. code-block:: python - - #django_freeradius/models.py - - from swapper import swappable_setting - - from .base.models import ( - AbstractNas, AbstractRadiusAccounting, AbstractRadiusBatch, AbstractRadiusCheck, AbstractRadiusGroup, - AbstractRadiusGroupCheck, AbstractRadiusGroupReply, AbstractRadiusPostAuth, AbstractRadiusReply, - AbstractRadiusToken, AbstractRadiusUserGroup, - ) - - - class RadiusCheck(AbstractRadiusCheck): - class Meta(AbstractRadiusCheck.Meta): - abstract = False - swappable = swappable_setting('django_freeradius', 'RadiusCheck') - - -Migrations ----------- - -Swapper can also be used in Django 1.7+ migration scripts to facilitate dependency ordering and -foreign key references. To use this feature in your library, generate a migration script with makemigrations -and make the following changes: - -.. code-block:: python - - #django_freeradius/migrations - - import swapper - - - class Migration(migrations.Migration): - - initial = True - - dependencies = [ - swapper.dependency('django_freeradius', 'RadiusReply'), - swapper.dependency('django_freeradius', 'RadiusCheck'), - ] - - operations = [ - migrations.CreateModel( - name='Nas', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('nas_name', models.CharField(db_column='nasname', db_index=True, help_text='NAS Name (or IP address)', max_length=128, unique=True, verbose_name='nas name')), - ('short_name', models.CharField(db_column='shortname', max_length=32, verbose_name='short name')), - ('type', models.CharField(max_length=30, verbose_name='type')), - ('secret', models.CharField(help_text='Shared Secret', max_length=60, verbose_name='secret')), - ('ports', models.IntegerField(blank=True, null=True, verbose_name='ports')), - ('community', models.CharField(blank=True, max_length=50, null=True, verbose_name='community')), - ('description', models.CharField(max_length=200, null=True, verbose_name='description')), - ('server', models.CharField(max_length=64, null=True, verbose_name='server')), - ], - options={ - 'db_table': 'nas', - 'swappable': swapper.swappable_setting('django_freeradius', 'Nas'), - 'verbose_name': 'nas', - 'abstract': False, - 'verbose_name_plural': 'nas', - }, - ), - -Extends Models --------------- - -The user of your app can override one or both models in their own app. - -Example: - -.. code-block:: python - - #sample_radius/models.py - - from django.db import models - from django.utils.translation import ugettext_lazy as _ - - from django_freeradius.models import (AbstractNas, AbstractRadiusAccounting, - AbstractRadiusCheck, - AbstractRadiusGroupCheck, AbstractRadiusGroupReply, - AbstractRadiusPostAuth, - AbstractRadiusReply, AbstractRadiusUserGroup) - - - class RadiusCheck(AbstractRadiusCheck): - details = models.CharField( - verbose_name=_('details'), max_length=64, blank=True, null=True) - - -Add swapper.load_model() to sample_radius/admin.py. Example: - -.. code-block:: python - - #sample_radius/admin.py - - from django.contrib import admin - - import swapper - from django_freeradius.admin import (AbstractRadiusGroupCheckAdmin, - AbstractRadiusGroupReplyAdmin, - ) - - RadiusGroupReply = swapper.load_model("django_freeradius", "RadiusGroupReply") - RadiusGroupCheck = swapper.load_model("django_freeradius", "RadiusGroupCheck") - - - @admin.register(RadiusCheck) - class RadiusGroupCheckAdmin(AbstractRadiusGroupCheckAdmin): - pass - - - @admin.register(RadiusCheck) - class RadiusGroupReplyAdmin(AbstractRadiusGroupReplyAdmin): - pass - - ---------------- -Update Settings ---------------- - -Update the settings to trigger the swapper: - -.. code-block:: python - - #django_freeradius/tests/settings.py - - if os.environ.get('SAMPLE_APP', False): - INSTALLED_APPS.append('sample_radius') - DJANGO_FREERADIUS_RADIUSREPLY_MODEL = "sample_radius.RadiusReply" - DJANGO_FREERADIUS_RADIUSGROUPREPLY_MODEL = "sample_radius.RadiusGroupReply" - DJANGO_FREERADIUS_RADIUSCHECK_MODEL = "sample_radius.RadiusCheck" - DJANGO_FREERADIUS_RADIUSGROUPCHECK_MODEL = "sample_radius.RadiusGroupCheck" - DJANGO_FREERADIUS_RADIUSACCOUNTING_MODEL = "sample_radius.RadiusAccounting" - DJANGO_FREERADIUS_NAS_MODEL = "sample_radius.Nas" - DJANGO_FREERADIUS_RADIUSUSERGROUP_MODEL = "sample_radius.RadiusUserGroup" - DJANGO_FREERADIUS_RADIUSPOSTAUTHENTICATION_MODEL = "sample_radius.RadiusPostAuth" diff --git a/docs/source/general/extend_django_freeradius.rst b/docs/source/general/extend_django_freeradius.rst new file mode 100644 index 0000000..8a16391 --- /dev/null +++ b/docs/source/general/extend_django_freeradius.rst @@ -0,0 +1,148 @@ +====================================== +Customizing django-freeradius +====================================== + +`django-freeeadius` provieds set of models, admin and API classes which can be imported, extende and hence customized by third party apps. + + +Extending models +---------------- +Apart from extending implemented models, `django_freeradius` also provides flexibility to extend abstract class models from `django-freeradius.base.models`. + +Example: + +.. code-block:: python + + #In sample_radius/models.py + + from django.db import models + from django_freeradius.base.models import AbstractRadiusCheck + + class RadiusCheck(AbstractRadiusCheck): + #modify/extend the default behavour here + + custom_field = models.TextField() + + +Extending admin +--------------- + +Similar to models, abstract admin classes from `django_freeradius.base.admin` can also be extended to avoid duplicate code. + +.. code-block:: python + + # In sample_radius/admin.py + + from django.contrib import admin + from .models import RadiusCheck + from django_freeradius.base.admin import AbstractRadiusAccountingAdmin + + class RadiusCheckAdmin(AbstractRadiusCheckAdmin): + model = RadiusCheck + + #modify/extend default behaviour here + + def __init__(self,*args,**kwargs): + #add your custom fields here + + self.fields.append('custom_field') + self.list_display.append('custom_field') + + super(AbstractRadiusCheckAdmin, self).__init__(*args, **kwargs) + + admin.site.register(RadiusCheck,RadiusCheckAdmin) + + + +Extending API views +------------------- + +Example of code: + +.. code-block:: python + + #django_freeradius/base/admin.py + + from django.contrib.admin import ModelAdmin + from openwisp_utils.admin import TimeReadonlyAdminMixin + + + class TimeStampedEditableAdmin(TimeReadonlyAdminMixin, ModelAdmin): + pass + + + class AbstractRadiusReplyAdmin(TimeStampedEditableAdmin): + list_display = ['username', 'attribute', 'op', + 'value', 'created', 'modified'] + autocomplete_fields = ['user'] + form = ModeSwitcherForm + fields = ['mode', + 'user', + 'username', + 'attribute', + 'op', + 'value', + 'created', + 'modified'] + + +Creating a Reusable App +----------------------- + +Install `swapper` to begin with this. If your reusable app is being published as a Python package, +be sure to add `swapper` to your project's dependencies. Learn more about `swapper` at the `Swapper Guide +` + +Install swapper: + +.. code-block:: shell + + pip install swapper + + +In your reusable models, use ``import swapper`` and add to Meta class ``swappable = swapper.swappable_setting('reusable_app', 'model')``: + +.. code-block:: python + + #django_freeradius/models.py + + from swapper import swappable_setting + + from .base.models import ( + AbstractNas, AbstractRadiusAccounting, AbstractRadiusBatch, AbstractRadiusCheck, AbstractRadiusGroup, + AbstractRadiusGroupCheck, AbstractRadiusGroupReply, AbstractRadiusPostAuth, AbstractRadiusReply, + AbstractRadiusToken, AbstractRadiusUserGroup, + ) + + + class RadiusCheck(AbstractRadiusCheck): + class Meta(AbstractRadiusCheck.Meta): + abstract = False + swappable = swappable_setting('django_freeradius', 'RadiusCheck') + + +.. note:: + Swapper can also be used in Django 1.7+ migration scripts to facilitate dependency ordering and + foreign key references. To use this feature in your library, generate a migration script with makemigrations + and make the following changes: + +--------------- +Update Settings +--------------- + +Update the settings to trigger the swapper: + +.. code-block:: python + + #django_freeradius/tests/settings.py + + if os.environ.get('SAMPLE_APP', False): + INSTALLED_APPS.append('sample_radius') + DJANGO_FREERADIUS_RADIUSREPLY_MODEL = "sample_radius.RadiusReply" + DJANGO_FREERADIUS_RADIUSGROUPREPLY_MODEL = "sample_radius.RadiusGroupReply" + DJANGO_FREERADIUS_RADIUSCHECK_MODEL = "sample_radius.RadiusCheck" + DJANGO_FREERADIUS_RADIUSGROUPCHECK_MODEL = "sample_radius.RadiusGroupCheck" + DJANGO_FREERADIUS_RADIUSACCOUNTING_MODEL = "sample_radius.RadiusAccounting" + DJANGO_FREERADIUS_NAS_MODEL = "sample_radius.Nas" + DJANGO_FREERADIUS_RADIUSUSERGROUP_MODEL = "sample_radius.RadiusUserGroup" + DJANGO_FREERADIUS_RADIUSPOSTAUTHENTICATION_MODEL = "sample_radius.RadiusPostAuth" diff --git a/docs/source/index.rst b/docs/source/index.rst index 71905dc..773e7c9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -34,7 +34,7 @@ Django-freeradius is part of the `OpenWISP project `_. /general/registration /general/social_login /general/api - /general/abstract_models + /general/extend_django_freeradius.rst /general/contributing /general/goals