django-CRadmin documentation

Getting started

Install and configure

Install

Install django_cradmin:

$ pip install django_cradmin

Configure

Add django_cradmin and crispy_forms to the INSTALLED_APPS setting:

INSTALLED_APPS = (
    ...
    'django_cradmin',
    'crispy_forms',
    ...
)

Add django.core.context_processors.request and django_cradmin.context_processors.cradmin to the TEMPLATE_CONTEXT_PROCESSORS setting:

TEMPLATE_CONTEXT_PROCESSORS = (
    "django.contrib.auth.context_processors.auth",
    "django.core.context_processors.debug",
    "django.core.context_processors.i18n",
    "django.core.context_processors.media",
    "django.core.context_processors.static",
    "django.core.context_processors.tz",
    "django.contrib.messages.context_processors.messages",
    "django.core.context_processors.request",
    "django_cradmin.context_processors.cradmin",
)

Set the CRISPY_TEMPLATE_PACK setting to bootstrap3:

CRISPY_TEMPLATE_PACK = 'bootstrap3'

Newbie guide

What is CRadmin

  • CRadmin has rolebased accesscontrol.
  • CRadmin has one or more apps, called CrApps.
  • CRadmin as one or more files called crinstance, which holds all the CrApps.

CRadmin_instance

When implementing CRadmin in a Django project, at least one file called crinstance_<crappname>.py must be created. This file holds different information about your CrApps, such as roleclass and method for rolequeryset. A cradmin_instance can holde one or more CrApps. It is possible for a cradmin_instance file to hold CrApps from different Django Apps.

One advantage with the cradmin_instance is the possibility to easily include different views. These views may have different roles.

Minimalistic cradmin_instance example:

class MyCrAdminInstance(crinstance.BaseCrAdminInstance):
roleclass =
rolefrontpage_appname =

apps = [
   ('CrAppNameHere, <path_to_crapp_module>.App),
]

def get_rolequeryset(self):
Roleclass

An easy way to explain the roleclass is to use the example. A website holds all the webpages. A very lightweight translation into Python classes can be the classes Site and Page. The cradmin_instance is then the Site, setting the roleclass to Site. You request the roleclass with request.cradmin_role. We will come back to the roleclass for Page later in the document.

Rolefrontpage appname

This is the app to where the user is redirected after chosing a role. Depending on what you build you may have different apps to where you want to redirect a user. This is solved by creating more than one cradmin_instance for the CrApp. However, you can also redirect a not logged-in user to the INDEX of your app, which would be the case if you build a website open for the public to read.

Rolequeryset

The super django_cradmin.crinstance.BaseCrAdminInstance describes more in detail the different methods used by a cradmin_intance. However if you use the rolebased accesscontrol, the django_cradmin.crinstance.BaseCrAdminInstance.get_rolequeryset() gets the role for the authenticated user. However, if you don’t need a role like in a detailview for a public webpage, you can use the mixin django_cradmin.crinstance.NoRoleMixin. There is also a mixin for not requiring a logged-in user in the django_cradmin.crinstance.NoLoginMixin. Furthermore the mixin django_cradmin.crinstance.NoLoginMixin is a shortcut if you neither need a role nor login to see the content held by the cradmin_instance.

CrApps

The module crapps is at the same level as templates and tests in a Django projects. Inside the crapps directory you will add modules which is the CRadmin app you want to use in your Django project. The project structure will then look something like:

django_app
    crapps (module)
        cradmin_app (module)
            __init__ (with urls)
Roles within CrApps

If we continue the example with a Website and webpages as mentioned earlier on, you may create a cradmin_app named pages_app. Within this app you will most likely have different kind of views, such as edit, create and delte. Lets assume the same person should be able to create, edit and delete a webpage. Somewhere, maybe in a mixin, you must define the role a user has to have to be granted privileges for working with the pages. The attribute roleid_field must be sat equal to the roleclass defined in the cradmin_instance file. Finally your views must have a method named get_queryset_for_role to set the correct access for the user. To make it all work, you will have the following classes:

MODELS
class Site(models.Model):
    name = models.CharField(max_length=100)
    admins = models.ManyToManyField(settings.AUTH_USER_MODEL)

class Page(models.Model):
    site = models.ForeginKey(Site)


CRADMIN INSTANCE
class WebCrAdminInstance(crinstance.BaseCrAdminInstance):
    id = 'website'
    roleclass = Site
    rolefrontpage_appname = 'pages'

    apps = [
        ('pages', pages.App),
    ]

    def get_rolequeryset(self):
        queryset = Site.objects.all()
        if not self.request.user.is_superuser:
            queryset = queryset.filter(admins=self.request.user)
        return queryset

PAGES APP
class PageCreateUpdateMixin(object):
    model = Page
    roleid_field = 'site'

    def get_queryset_for_role(self):
        return Page.objects.filter(site=self.request.cradmin_role)
Views in CrApps

There are different types of views within CRadmin. It is important to remember that this is an Django Extension, so if you don’t know much about views in Django, do start reading the Django Docs. However, the view used for edit a webpage will be a subclass of the super django_cradmin.viewhelpers.formview.updateview.WithinRoleUpdateView. This is a modelbased view, and offcourse there are super classes for create and delete. Ssometimes a modelbased view just want cut it. In these cases, the django_cradmin.viewhelpers.formview.formview.WithinRoleFormView may be your super class. The point is to use the viewhelpers in CRadmin.

Indexview

According to django_cradmin.crapp.App.reverse_appindexurl() it is expected that each CrApp has a view named crapp.INDEXVIEW_NAME. This is the frontpage or homepage for the app.

CRadmin urls

We recomend to use the __init__ file within a cradmin__app to set the urls for each view. Hence the file contaning you default urls must include the urls to the cradmin_instance:

url(r'^webpages/', include(WebCrAdminInstance.urls())),

In the __init__ file you will add the django_cradmin.crapp.App which holds the urls to all different views within the app. If we continue the website and webpage example, the __init__ file will look something like this for the pages app:

from django_cradmin import crapp

from django_project.django_app.crapps.pages_app import websiteviews
from django_project.django_app.crapps.pages_app import editviews

class App(crapp.App):
    appurls = [
        crapp.Url(
            r'^$',
            websiteviews.IndexView.as_view(),
            name=crapp.INDEXVIEW_NAME
        ),
        crapp.Url(
            r'^create$',
            editviews.PageCreateView.as_view(),
            name="create"),
        crapp.Url(
            r'^edit/(?P<pk>\d+)$',
            editviews.PageUpdateView.as_view(),
            name="edit"),
        crapp.Url(
            r'^delete/(?P<pk>\d+)$',
            editviews.PageDeleteView.as_view(),
            name="delete")
    ]

Viewhelpers

viewhelpers.delete — Implements the preview+confirm+delete workflow

When to use

Use this when you need a view to delete a single item. Your users will get a preview of the item, and the option to confirm the delete or cancel the delete.

Usage

The django_cradmin.viewhelpers.delete.DeleteView is just a subclass of django.views.generic.DeleteView, so you use it just like the Django DeleteView.

Very basic example (no security)

Lets say you have the following Page-model in models.py:

from django.conf import settings

class Page(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()

    def __unicode__(self):
        return self.title

Then you would create a PageDeleteView and register it in an app with the following code:

from django_cradmin.viewhelpers import delete
from django_cradmin import crapp

class PageDeleteView(delete.DeleteView):
    """
    View used to delete existing pages.
    """
    model = Page

class App(crapp.App):
    appurls = [
        # .. other views
        crapp.Url(r'^delete/(?P<pk>\d+)$',
            PageDeleteView.as_view(),
            name="delete"),
    ]
Securing the basic example

The basic example lets anyone with access to the cradmin delete any page. You normally have multiple roles, and each role will have access to a subset of objects. Lets add a role class named Site, and extend our Page-model with a foreign-key to that site. Our new models.py looks like this:

from django.conf import settings
from django.db import models

class Site(models.Model):
    name = models.CharField(max_length=100)
    admins = models.ManyToManyField(settings.AUTH_USER_MODEL)

class Page(models.Model):
    site = models.ForeignKey(Site)
    title = models.CharField(max_length=100)
    body = models.TextField()

    def __unicode__(self):
        return self.title

We make the Site the roleclass on our MyCrAdminInstance:

from django_cradmin import crinstance
class MyCrAdminInstance(crinstance.BaseCrAdminInstance):
    roleclass = Site
    # Other stuff documented elsewhere

We only want to allow deleting within the current Site (current role), so we replace model on the DeleteView with a get_queryset-method that limits the pages to pages within the current site:

class PageDeleteView(delete.DeleteView):
    """
    View used to delete existing pages.
    """
    def get_queryset(self):
        return Page.objects.filter(site=self.request.cradmin_role)

Writing tests

Writing tests - writing tests for cradmin instances and apps

Writing tests for a CrInstance

Like this:

from unittest import mock

from django.conf import settings
from django.test import TestCase
from model_mommy import mommy

from exampledjangoapps.exampleapp_dummyname.crinstances.crinstance_question import QuestionCrAdminInstance


class TestQuestionCrAdminInstance(TestCase):

    def test_user_that_is_not_superuser_makes_rolequeryset_empty(self):
        mommy.make('exampleapp_dummyname.Question')
        mockrequest = mock.MagicMock()
        mockrequest.user = mommy.make(settings.AUTH_USER_MODEL)
        crinstance = QuestionCrAdminInstance(request=mockrequest)
        self.assertEqual(0, len(crinstance.get_rolequeryset().all()))

Writing tests for a CrApp

Testhelpers — Makes it easier to write tests for your cradmin views

Test case mixins

class MockRequestResponse(response, request)

Bases: object

Return type of TestCaseMixin.mock_request(), TestCaseMixin.mock_http200_getrequest_htmls() and TestCaseMixin.mock_postrequest().

response

The HttpResponse object.

request

The RequestFactory-generated HttpRequest object.

selector

A htmls.S object created with the entire content of the response as input. Only available when using TestCaseMixin.mock_http200_getrequest_htmls().

class AbstractTestCaseMixin

Bases: object

Implements the common API for the test case mixins.

viewclass = None

The view class - must be set in subclasses unless you override get_viewclass().

get_viewclass()

Get the view class. Defaults to viewclass

make_view()

Make view function.

Calls get_viewclass().as_view() by default.

get_requestfactory_class()

Get the request factory class.

Must be implemented in subclasses.

create_default_user_for_mock_request()

Create default user for mock request.

Defaults to returning mommy.make(settings.AUTH_USER_MODEL), which should create a user no matter what user model you are using, unless you do something very complex for users.

You can override this if you want to change the default properties of users for all tests in this testcase, but it is normally better to just use the requestuser kwarg for mock_request() (or any of the more specialized mock_*request(..) methods)

Must return a user object compatible with django.http.HttpRequest.user. This means that the method can also return an AnonymousUser, but if you need this you should use NoLoginTestCaseMixin (which just overrides this method).

make_minimal_request(method, requestkwargs=None, httpheaders=None)

Make a minimal request object.

Parameters:method (str) – The http method (get, post, ...).
:param requestkwargs:: Kwargs for the request. Defaults to
{'path': '/'}, and the path is added unless it is included in the dict. The data you can include in this dict is the same as for the get, post, ... methods on django.test.Client (depends on the method kwarg).

:type requestkwargs:: dict :param httpheaders:: Extra http headers needed.

:type httpheaders:: dict

Returns:The created request object.
prettyformat_response_content(response)

Must be implemented in subclasses.

mock_request(method, cradmin_role=None, cradmin_app=None, cradmin_instance=None, requestuser=None, messagesmock=None, sessionmock=None, requestattributes=None, httpheaders=None, requestkwargs=None, viewkwargs=None, expected_statuscode=None, htmls_selector=False, verbose=False)

Create a mocked request using make_requestfactory() and mock.MagicMock.

Parameters:method – The http method (get, post, ...).
:param httpheaders:: Forwarded to make_minimal_request().
Extra HTTP headers to be added to request.META.

:type httpheaders:: httpheaders :param requestkwargs:: Forwarded to make_minimal_request().

:type requestkwargs:: dict :param cradmin_role: The request.cradmin_role to use. Defaults to mock.MagicMock(). :param cradmin_app: The request.cradmin_app to use. Defaults to mock.MagicMock(). :param cradmin_instance: The request.cradmin_instance to use. Defaults to mock.MagicMock(). :param requestuser: The request.requestuser to use. Defaults to mock.MagicMock(). :param sessionmock: The request.session to use. Defaults to mock.MagicMock(). :param messagesmock: The request._messages to use. Defaults to mock.MagicMock().

Parameters:
  • requestattributes (dict) – Extra attributes to the reques to object. This is applied before add_essential_cradmin_attributes_to_request(), so any of the attributes that method sets can not be in this dics.
  • viewkwargs (dict) – Kwargs for the view.
  • expected_statuscode (int) – Expected status code. If this is None, we do not validate the status code. Defaults to None.
  • htmls_selector (boolean) – If this is True, we create a htmls.S() object for the response content. Should normally not be used without also providing expected_statuscode because it can lead to unexpected behavior. Defaults to False.
  • verbose (boolean) – More verbose exception messages. Useful for debugging, but should be False when you are not debugging. Defaults to False.
class TestCaseMixin

Bases: django_cradmin.cradmin_testhelpers.AbstractTestCaseMixin

A mixin class that makes it easier to write tests for cradmin views.

It mocks all the required attributes of the request object.

Examples

Minimalistic:

from django_cradmin import cradmin_testhelpers

class TestMyView(TestCase, cradmin_testhelpers.TestCaseMixin):
    viewclass = MyView

    def test_get_render_form(self):
        mockresponse = self.mock_http200_getrequest_htmls()
        mockresponse.selector.prettyprint()

    def test_post(self):
        mockresponse = self.mock_postrequest(requestkwargs={
            'data': {
                'name': 'Jane Doe',
                'age': 24
            }
        })
        self.assertEqual(mockresponse.response.status_code, 302)

Adding optional HTTP headers if needed:

from django_cradmin import cradmin_testhelpers

class TestMyView(TestCase, cradmin_testhelpers.TestCaseMixin):
    viewclass = MyView

    def test_post_with_optional_httpheaders(self):
        self.mock_postrequest(
            cradmin_instance=cradmin_instance,
            httpheaders={
                'HTTP_REFERER': 'http://www.http-referent.com'
            },
            requestkwargs={
                'data': {
                    'name': 'Jane Doe',
                    'age': 24
                }
            })
        self.assertEqual(mockresponse.response.status_code, 302)

Customizing the request.body, if e.g you have created a view that works as an API that handles POST with data. This data will in most cases be JSON. If this is not done through a form, but javascript, the data will end up in request.body, and not request.POST. To have this functionality when testing, simply add the data you want to _body in requestattributes. request._body is the actual attribute, and request.body is a property:

from django_cradmin import cradmin_testhelpers

class TestMyView(TestCase, cradmin_testhelpers.TestCaseMixin):
    viewclass = MyView

    def test_post_with_optional_httpheaders(self):
        self.mock_postrequest(
            cradmin_instance=cradmin_instance,
            requestattributes={
                '_body': b'{'key': 'value'}'
            })
        self.assertEqual(mockresponse.response.status_code, 302)

Views that take arguments:

from django_cradmin import cradmin_testhelpers

class TestMyView(TestCase, cradmin_testhelpers.TestCaseMixin):
    viewclass = MyView

    def test_get_render_form(self):
        # The kwargs for mock_postrequest, mock_http200_getrequest_htmls
        # and mock_getrequest are the same, so only showing one for brevity.
        mockresponse = self.mock_http200_getrequest_htmls(viewkwargs={'pk': 10})

Views that use a querystring (GET):

from django_cradmin import cradmin_testhelpers

class TestMyView(TestCase, cradmin_testhelpers.TestCaseMixin):
    viewclass = MyView

    def test_get_render_form(self):
        mockresponse = self.mock_http200_getrequest_htmls(
            requestkwargs={
                'data': {
                    'orderby': 'name'
                }
            }
        )

Using a real user object:

from django_cradmin import cradmin_testhelpers

class TestMyView(TestCase, cradmin_testhelpers.TestCaseMixin):
    viewclass = MyView

    def test_post(self):
        requestuser = mommy.make(settings.AUTH_USER_MODEL)
        mockresponse = self.mock_http200_getrequest_htmls(requestuser=requestuser)

Mocking Django messages framework messages:

from django_cradmin import cradmin_testhelpers

class TestMyView(TestCase, cradmin_testhelpers.TestCaseMixin):
    viewclass = MyView

    def test_post(self):
        messagesmock = mock.MagicMock()
        mockresponse = self.mock_postrequest(
            requestkwargs={
                'data': {
                    'name': 'Jane Doe',
                    'age': 24
            },
            messagesmock=messagesmock
        )
        messagesmock.add.assert_called_once_with(
            messages.SUCCESS,
            'The data was posted successfully!',
            '')

Get a bit of extra information useful while debugging failing tests - use verbose=True (only for debugging, do not keep/commit verbose=True):

from django_cradmin import cradmin_testhelpers

class TestMyView(TestCase, cradmin_testhelpers.TestCaseMixin):
    viewclass = MyView

    def test_get(self):
        mockresponse = self.mock_http200_getrequest_htmls(
            verbose=True
        )
get_requestfactory_class()

Get the request factory class. Defaults to django.test.RequestFactory.

class NoLoginTestCaseMixin

Bases: django_cradmin.cradmin_testhelpers.TestCaseMixin

Use this instead of TestCaseMixin if you do not require an authenticated user in your view.

create_default_user_for_mock_request()

Overridden to return a django.contrib.auth.models.AnonymousUser object instead of creating a user.

class RestFrameworkApiTestCaseMixin

Bases: django_cradmin.cradmin_testhelpers.TestCaseMixin

Django-rest-framework API test case mixin.

Works just like TestCaseMixin, except that it has a different debug output, and no *_htmls methods.

Assumes that the viewclass is a subclass of rest_framework.views.APIView.

You can also make it work with viewsets by overriding make_view() and use return ViewSetClass.as_view({...}) there.

Utilities

Css classes only for automatic tests

You should use the cradmin_test_css_class() template tag to add css classes that are only used in tests.

If you extend django_cradmin.renderable.AbstractRenderableWithCss, you can add css classes only for tests by overriding get_test_css_class_suffixes_list().

Modelhelpers

sortable — Making objects sortable

To make a model sortable, you need the following two additions to your model:

  1. You need to inherit from SortableBase (an abstract model) instead of django.db.Model,
  2. You need to create a subclass of SortableItemQuerySet and attach that subclass as a queryset for your model.

Example:

from django_cradmin.sortable.models import SortableBase
from django_cradmin.sortable.models import SortableQuerySetBase

class MySortableItemQuerySet(SortableQuerySetBase):
    parent_attribute = 'container'

class MySortableItem(SortableBase):
    objects = MySortableItemQuerySet.as_manager()
    container = models.ForeignKey(ItemContainer, blank=False, null=False)
    name = models.CharField(...)

The class that inherits SortableBase gets an attribute sort_index. If you want the default ordering for this model to be this attribute, you should add the following meta option on the model:

class Meta:
    ordering = ['sort_index']

How to sort

Sorting is done by using these methods:

Example:

# Make a new item and put it last in the list
myitem = MySortableItem(container=somecontainer, name='Some name')
MySortableItem.objects.set_newitem_sort_index_to_last(myitem)
myitem.save()

# Move the given item before the item with id 777
# NOTE: Unlike set_newitem_sort_index_to_last(), sort_before() and sort_last()
#       saves the item.
MySortableItem.objects.sort_before(someitem, sort_before_id=777)

# Move the given item last in the list
MySortableItem.objects.sort_last(someitem)
# ... or ...
MySortableItem.objects.sort_before(someitem, sort_before_id=None)

Makin an Admin UI that automatically adds items last in parent

Making an Admin UI that automatically adds items last in parent is easy. Just extend django_cradmin.sortable.admin.SortableModelAdmin instead of django.contrib.admin.ModelAdmin:

from django_cradmin.sortable.admin import SortableModelAdmin

class MySortableItemAdmin(SortableModelAdmin):
    pass

admin.site.register(models.MySortableItem, MySortableItemAdmin)

You may also want to show the sort order by default in the admin UI listing, with something like this:

class MySortableItemAdmin(SortableModelAdmin):
    ordering = ['container__name', 'sort_index']

API

class SortableQuerySetBase(model=None, query=None, using=None, hints=None)

Bases: django_cradmin.utils.nulls_last_queryset.NullsLastQuerySet

QuerySet for SortableManagerBase.

You must use this as a base class if you want to create a custom queryset class for models extending SortableBase.

set_newitem_sort_index_to_last(item, none_values_order_by=None)

Sets item.sort_index to the sort_index of the last item in the parent + 1. Does not save.

ONLY USE THIS FOR NEWLY CREATED ITEMS.

sort_before(item, sort_before_id, none_values_order_by=None)

Sort a given item before the item with id sort_before_id, or last if sort_before_id is None.

Fetches all items in the same container, and makes changes in the ordering. Only the required updates are made.

sort_last(item, none_values_order_by=None)

Just a shortcut for:

self.sort_before(item, sort_before_id=None)
class SortableBase(*args, **kwargs)

Bases: django.db.models.base.Model

Used with SortableQuerySetBase to make models sortable.

sort_index

Sort index - 0 or higher.

class SortableModelAdmin(model, admin_site)

Bases: django.contrib.admin.options.ModelAdmin

ModelAdmin that automatically sets the sort_index of newly added items last in their parent. It also makes sort_index read-only by default.

Used just like django.contrib.admin.ModelAdmin.

make_sort_index_readonly = False

If this is True , we make the sort_index field read-only. Override this to avoid this magic (typically for debugging).

save_model(request, obj, form, change)

Overridden to set the sortindex on save if the pk is None.

get_readonly_fields(request, obj=None)

Overridden to make the sortindex readonly if make_sort_index_readonly is True.

Apps that provide utitities for other apps

cradmin_generic_token_with_metadata — Secure generic tokens with metadata

The purpose of the django_cradmin.apps.cradmin_generic_token_with_metadata app is to provide secure and unique tokens with attached metadata. The tokens are suitable for email confirmation workflows and public share urls.

Each token belongs to a user and an app. Tokens only live for a limited time, and this time can be configured on a per app basis.

Very useful for single-use URLs like password reset, account activation, etc.

How it works

Lets say you have an object and want to generate a unique token for that object:

from django_cradmin.apps.cradmin_generic_token_with_metadata.models import GenericTokenWithMetadata
from django.contrib.auth import get_user_model

myuser = get_user_model().get(...)  # The user is the object the token is for.
generictoken = GenericTokenWithMetadata.objects.generate(
    app='myapp', content_object=myuser,
    expiration_datetime=get_expiration_datetime_for_app('myapp'))
# Use generictoken.token

This creates a GenericTokenWithMetadata object with a token-attribute that contains a unique token. The app is provided for two reasons:

  • Makes it easier to debug/browse the data model because you know what app generated the token.
  • Makes it possible to configure different time to live for each app.
  • Isolation. Each app has their own “namespace” of tokens.

When you have a token, typically from part of an URL, and want to get the user owning the token, use:

generictoken = GenericTokenWithMetadata.objects.pop(app='myapp', token=token)
# Use generictoken.user and generictoken.metadata

This returns the GenericTokenWithMetadata, and deletes the GenericTokenWithMetadata from the database.

Use case — password reset email

Lets say you want to use GenericTokenWithMetadata to generate a password reset email.

First, we want to give the user an URL where they can go to reset the password:

url = 'http://example.com/resetpassword/{}'.format(
    GenericTokenWithMetadata.objects.generate(
        app='passwordreset',
        content_object=self.request.user,
        expiration_datetime=get_expiration_datetime_for_app('passwordreset'))

Since we are using Django, we will most likely want the url to be to a view, so this would most likely look more like this:

def start_password_reset_view(request):
    url = request.build_absolute_uri(reverse('my-reset-password-accept-view', kwargs={
        'token': GenericTokenWithMetadata.objects.generate(
            app='passwordreset', content_object=self.request.user,
            expiration_datetime=get_expiration_datetime_for_app('passwordreset'))
    }
    # ... send an email giving the receiver instructions to click the url

In the view that lives at the URL that the user clicks to confirm the password reset request, we do something like the following:

class ResetThePassword(View):
    def get(request, token):
        try:
            token = GenericTokenWithMetadata.objects.get_and_validate(app='passwordreset', token=token)
        except GenericTokenWithMetadata.DoesNotExist:
            return HttpResponse('Invalid password reset token.')
        except GenericTokenExpiredError:
            return HttpResponse('Your password reset token has expired.')
        else:
            # show a password reset form

    def post(request, token):
        try:
            token = GenericTokenWithMetadata.objects.pop(app='passwordreset', token=token)
        except GenericTokenWithMetadata.DoesNotExist:
            return HttpResponse('Invalid password reset token.')
        else:
            # reset the password

Configure

You can configure the time to live of the generated tokens using the DJANGO_CRADMIN_SECURE_USER_TOKEN_TIME_TO_LIVE_MINUTES setting:

DJANGO_CRADMIN_SECURE_USER_TOKEN_TIME_TO_LIVE_MINUTES = {
    'default': 1440,
    'myapp': 2500
}

It defaults to:

DJANGO_CRADMIN_SECURE_USER_TOKEN_TIME_TO_LIVE_MINUTES = {
    'default': 60*24*4
}

Delete expired tokens

To delete expired tokens, you can use:

GenericTokenWithMetadata.objects.delete_expired()

or the cradmin_generic_token_with_metadata_delete_expired management command:

$ python manage.py cradmin_generic_token_with_metadata_delete_expired

Note

You do not need to delete expired tokens very often unless you generate a lot of tokens. Expired tokens are not available through the GenericTokenWithMetadataBaseManager.pop() method. So if you use the API as intended, you will never use an expired token.

API

get_time_to_live_minutes(app)

Get the configured time to live in minutes for tokens for the given app.

get_expiration_datetime_for_app(app)

Get the expiration datetime of tokens for the given app relative to now.

If the given app is configured to with 60 minutes time to live, this will return a datetime object representing 60 minutes in the future.

generate_token()

Generate a token for the GenericTokenWithMetadata.token field.

Joins an UUID1 (unique uuid) with an UUID4 (random uuid), so the chance of this not beeing unique is very low, and guessing this is very hard.

Returns:A token that is very unlikely to not be unique.
exception GenericTokenExpiredError

Bases: Exception

Raised by GenericTokenWithMetadata.get_and_validate() when the token is found, but has expired.

class GenericTokenWithMetadataQuerySet(model=None, query=None, using=None, hints=None)

Bases: django.db.models.query.QuerySet

QuerySet for GenericTokenWithMetadata.

unsafe_pop(app, token)

Get the GenericTokenWithMetadata matching the given token and app. Removes the GenericTokenWithMetadata from the database, and returns the GenericTokenWithMetadata object.

You should normally use GenericTokenWithMetadataBaseManager.pop() instead of this.

Raises:
  • GenericTokenWithMetadata.DoesNotExist if no matching token is stored for
  • the given app.
filter_has_expired()

Return a queryset containing only the expired GenericTokenWithMetadata objects in the current queryset.

filter_not_expired()

Return a queryset containing only the un-expired GenericTokenWithMetadata objects in the current queryset.

filter_by_content_object(content_object)

Filter by GenericTokenWithMetadata.content_object.

Examples

Lets say the content_object is a User object, you can find all tokens for that user in the page_admin_invites app like this:

from django.contrib.auth import get_user_model
user = get_user_model()
GenericTokenWithMetadata.objects                    .filter(app='page_admin_invites')                    .filter_by_content_object(user)
filter_usable_by_content_object_in_app(content_object, app)

Filters only non-expired tokens with the given content_object and app.

class GenericTokenWithMetadataBaseManager

Bases: django.db.models.manager.Manager

Manager for GenericTokenWithMetadata.

Inherits all methods from GenericTokenWithMetadataQuerySet.

generate(app, expiration_datetime, content_object, metadata=None)

Generate and save a token for the given user and app.

Returns:class:.GenericTokenWithMetadata object with a token that is guaranteed to be unique.
Return type:A
pop(app, token)

Get the GenericTokenWithMetadata matching the given token and app. Removes the GenericTokenWithMetadata from the database, and returns the GenericTokenWithMetadata object.

Does not return expired tokens.

Raises:GenericTokenWithMetadata.DoesNotExist – If no matching token is stored for the given app, or if the token is expired.
delete_expired()

Delete all expired tokens.

get_and_validate(app, token)

Get the given token for the given app.

Raises:
  • GenericTokenWithMetadata.DoesNotExist – If the token does not exist.
  • GenericTokenExpiredError – If the token has expired.
class GenericTokenWithMetadata(*args, **kwargs)

Bases: django.db.models.base.Model

Provides a secure token with attached metadata suitable for email and sharing workflows like password reset, public share urls, etc.

app

The app that generated the token. You should set this to the name of the app the generated the token.

token

A unique and random token, set it using generate_token().

created_datetime

Datetime when the token was created.

expiration_datetime

Datetime when the token expires. This can be None, which means that the token does not expire.

single_use

Single use? If this is False, the token can be used an unlimited number of times.

metadata_json

JSON encoded metadata

content_type

The content-type of the content_object. Together with object_id this creates a generic foreign key to any Django model.

object_id

The object ID of the content_object. Together with content_type this creates a generic foreign key to any Django model.

content_object

A django.contrib.contenttypes.fields.GenericForeignKey to the object this token is for.

This generic relationship is used to associate the token with a specific object.

Use cases:

  • Password reset: Use the content_object to link to a User object when you create password reset tokens.
  • Invites: Use the content_object to link to the object that you are inviting users to. This enables you to filter on the content object to show pending shares.
is_expired()

Returns True if GenericTokenWithMetadata.expiration_datetime is in the past, and False if it is in the future or now.

metadata

Decode GenericTokenWithMetadata.metadata_json and return the result.

Return None if metadata_json is empty.

Utilities

urlutils — Utilities for working with URLs

create_querydict(querystringargs, initial_query_string=None, ignore_none_values=True)
Parameters:
  • querystringargs (dict) – The querystring args to add/replace.
  • initial_query_string (str) – The initial querystring. Any ovelapping keys between this and querystringargs is overridden by the value in querystringargs.
  • ignore_none_values (bool) – If this is True (default), we ignore None values in querystringargs.
Returns:

class:django.http.request.QueryDict.

Return type:

The created

update_querystring(url, querystringargs, ignore_none_values=True)

Update the querystring portion of the given url.

Parameters:
  • querystringargs (dict) – The querystring args to add/replace.
  • ignore_none_values (bool) – If this is True (default), we ignore None values in querystringargs.
Returns:

The updated url.

Examples

Add querystring argument:

from django_cradmin import urlutils
urlutils.update_querystring('http://example.com', {'search': 'test'})

Update querystring argument:

urlutils.update_querystring('http://example.com?search=something&page=2',
    {'search': 'updated'})

renderable — Unified renderable interface

When you build decoupled modules where separate items is rendered, you often need to render several different templates into one output. One approach is to just use the {% include %} tag, but that is not a very object oriented approach. To make this object oriented, we use the django_cradmin.renderable.AbstractRenderable class to provide a unified interface inspired by the TemplateView class in Django.

To provide a renderable object, you simply subclass django_cradmin.renderable.AbstractRenderable, specify a template name, and add methods, attributes and properties to the class to make them available to the template.

join_css_classes_list(css_classes_list)

Join the provided list of css classes into a string.

class AbstractRenderable

Bases: object

An abstract class that implements an interface for rendering something.

Everything is just helpers for the render() method, which renders a template with an object of this class as input.

template_name = None

The default value for get_template_names().

get_template_names()

Get the template name(s) for render().

Defaults to template_name.

Raises:NotImplementedError – If template_name is not set.
get_context_data(request=None)

Get context data for render().

Defaults to:

{
    'me': self
}
render(request=None, extra_context_data=None)

Render get_template_names with the context returned by get_context_data().

Paramteters:
request (HttpRequest): If this is provided, we forward it to
get_context_data(), and to render_to_string() (which is used to render the template).
class AbstractRenderableWithCss

Bases: django_cradmin.renderable.AbstractRenderable

Extends AbstractRenderable with a unified API for setting CSS classes.

get_css_classes_list()

Override this to define css classes for the component.

Must return a list of css classes.

See get_css_classes_string().

get_test_css_class_suffixes_list()

List of css class suffixes to include when running automatic tests.

These suffixes are filtered through the cradmin_test_css_class() template tag.

css_classes

Get css classes.

Joins get_css_classes_list() into a string.

You should not override this, override get_css_classes_list() instead.

class AbstractBemRenderable(bem_block=None, bem_element=None, bem_variant_list=None)

Bases: django_cradmin.renderable.AbstractRenderable

Base class for renderables that uses BEM (http://getbem.com/) for their CSS class naming.

This is an alternative to AbstractRenderableWithCss that makes it much more natural to work with BEM.

Parameters:
  • bem_block (str) – Get the BEM block. Can not be supplied if bem_element is supplied.
  • bem_element (str) – Get the BEM element. Can not be supplied if bem_block is supplied.
  • bem_variant_list (list) – Get a list of BEM variants for the block/element. You do not include the block/element, just the part after --.
get_test_css_class_suffixes_list()

List of css class suffixes to include when running automatic tests.

These suffixes are filtered through the cradmin_test_css_class() template tag.

bem_block_or_element

Returns get_bem_block() falling back to get_bem_element().

get_bem_block()

Get the bem block string.

get_bem_element()

Get the bem element string.

get_bem_variant_list()

Get a list of BEM variants.

You do not include the block/element, just the part after --.

css_classes

Get css classes as a string.

You should not override this, override get_bem_block() / get_bem_element() and get_bem_variant_list() instead.

utils.crhumanize — Utilities for humanizing data

human_readable_filesize(size_in_bytes)

Humanize the given file size in bytes.

Returns a number suffixed with B, KB, MB, GB or TB.

Examples

>>> from django_cradmin.utils import crhumanize
>>> crhumanize.human_readable_filesize(1)
'1B'
>>> crhumanize.human_readable_filesize(2344234345)
'2.34GB'
>>> crhumanize.human_readable_filesize(23442343451234)
'23.44TB'
dehumanize_readable_filesize(humanized_size)

Does the opposite of human_readable_filesize().

Takes a string containing a number suffixed with B, KB, MB, GB or TB, and returns an int with the number of bytes.

Examples

>>> from django_cradmin.utils import crhumanize
>>> crhumanize.dehumanize_readable_filesize('999B')
999
>>> crhumanize.dehumanize_readable_filesize('2.34GB')
2340000000
>>> crhumanize.dehumanize_readable_filesize('43.312TB')
43312000000000

delay_middleware — Middleware for testing how your app behaves with latency

class DelayMiddleware

Bases: object

To use this, you must add the following to your settings:

  • Add django_cradmin.delay_middleware.DelayMiddleware to MIDDLEWARE_CLASSES.
  • Set DJANGO_CRADMIN_DELAY_MIDDLEWARE_MILLISECONDS to the number of milliseconds delay you want to add to all requests (I.E.: 2000 for 2 seconds).

messages_debug_middleware — Debug django messages rendering/styles

class MessagesDebugMiddleware(get_response)

Bases: object

Add django_cradmin.messages_debug_middleware.MessagesDebugMiddleware to your MIDDLEWARE_CLASSES setting to debug Django messages rendering/styling. Will add one of each message type to the request.

uicontainer — HTML builder with form support

Specifying CSS classes in uicontainer

TODO: Explain the various kwargs for styling.

uicontainer and BEM

We use BEM syntax for the cradmin CSS, and we have designed the uicontainer library with built in support for working with BEM.

This does not mean that you are forced to use BEM with uicontainer, it simly means that we have some shortcuts/convenience stuff that makes using BEM natural and easy.

More information about BEM at http://getbem.com/.

Development

Develop django_cradmin, or run the demo

To develop django-cradmin, or to run the demo, you will have to do the following.

Clone the git repo

You will find the URL on our github project page.

Create a virtualenv

$ mkvirtualenv -p /path/to/python3 django_cradmin

Install the development requirements

$ workon django_cradmin
$ pip install -r requirements/python3.txt

Create the demo database

$ workon django_cradmin
$ inv recreate_devdb

Run the development server

$ workon django_cradmin
$ python manage.py runserver

Open http://localhost:8000 and login with:

email: grandma@example.com
password: test

Run the tests

$ workon django_cradmin
$ DJANGOENV=test python manage.py test django_cradmin

Using and creating database dumps

We use dumpscript_ from the django-extentions Django app to create our test data. We already have data, so unless you want to add more data, you do not need to know anything more than how to run a Django management task or a fabric task.

Importing the test data

The easiest method of importing the test database is to use the recreate_devdb Fabric task:

$ ievv recreate_devdb

Warning

This will destroy your current database.

Users in the test database

After importing the test data, you will have some new users. Login to the Django admin UI (http://localhost:8000/admin/) with:

email: grandma@example.com
password: test

and select Users to list all users. The password of all users are test.

Add new data

To add new data, you just need to do add data to the database manually.

Adding data manually (I.E.: Using the Django admin UI)

To add data manually, you should first run the recreate_devdb management command to make sure you start out with the current up-to-date dataset. Then you can use the web-UI or the Django shell to add data. Finally, run:

$ ievv dump_db_as_sql

Now you can commit the update django_cradmin/demo/project/demo/dumps/default.sql to the git repo if you want to make the changes to the development database available to other developers of django-cradmin.

Localization/internationalization/translation

How we organize the translations

All translations are added to django_cradmin/locale/. We do not add translation per app for the following reasons:

  • There are lots of overlapping translation strings.
  • Easier to upload and maintain a single translation catalog on Transifex.

Configure Transifex

Before you can start pushing and pulling translation files to/from Transifex, you will need to create a ~/.transifexrc. It should look like this:

[https://www.transifex.com]
hostname = https://www.transifex.com
username = myuser
password = supersecret
token =

More information here: http://docs.transifex.com/developer/client/config.

Translation process

We translate using Transifex. This means that the workflow is:

  1. Mark new translations or change existing translations.
  2. Build the translation files (.po files).
  3. Push translation files (.po files) to Transifex.
  4. Wait for translators to translate using Transifex.
  5. Pull translation files (.po files) from Transifex.
  6. Compile translations and commit the .mo files.

Below we go in detail for each of these steps. All commands assume the following:

$ cd /path/to/reporoot
$ workon django_cradmin
Mark new translations or change existing translations

Read the Django internationalization docs.

Build the translation files

First, make sure you have the latest po-files from transifex:

$ tx pull

We have a fabric task for that:

$ inv makemessages

Commit the changes to the .po-files in django_cradmin/locale/.

Push translation files to Transifex

Run:

$ tx push -s -t

to push the .po files to transifex.

Compile translations and commit the .mo files

We have a fabric task for compiling the translations:

$ cd /path/to/reporoot
$ workon django_cradmin
$ inv compilemessages

This should change some .mo-files in django_cradmin/locale/. Commit those files.

How to release a new version of cradmin

Note

This assumes you have permission to release cradmin to pypi.

  1. Remove the previous built static files:

    $ git rm -r django_cradmin/apps/django_cradmin_js/static/django_cradmin_js/ django_cradmin/apps/django_cradmin_styles/static/django_cradmin_styles/
    
  2. Update django_cradmin/version.json.

  3. Run:

    $ ievv buildstatic --production
    

4: Run:

$ git add django_cradmin/apps/django_cradmin_js/static/django_cradmin_js/ django_cradmin/apps/django_cradmin_styles/static/django_cradmin_styles/
  1. Commit.
  2. Tag the commit with <version>.
  3. Push (git push && git push --tags).
  4. Release to pypi (python setup.py sdist && twine upload dist/django-cradmin-<version>.tar.gz).

Documentation

CRApp

INDEXVIEW_NAME = 'INDEX'

The name of the app index view (the landing page for the app). We do not enforce this, but we assume that each app has a view with this name.

class Url(regex, view, kwargs=None, name=None)

Bases: object

Url is mostly the same as func:django.conf.urls.url. You use Url to add urls to an app.

Parameters:
  • regex – The URL regex.
  • view – The view (E.g.: MyView.as_view()).
  • kwargs – Keyword arguments for the view.
  • name – The name of the view. This just have to be unique within the App - the actual URL name is generated based on the app name and the django_cradmin.crinstance.BaseCrAdminInstance.id.
class App(appname, request, active_viewname)

Bases: object

A cradmin App.

Added to a django_cradmin.crinstance.BaseCrAdminInstance with django_cradmin.crinstance.BaseCrAdminInstance.apps.

appurls = []

See get_appurls().

reverse_appurl(viewname, args=None, kwargs=None)

Works just like django.core.urlresolvers.reverse(), except that the name is the name given in appurls, not the full name of the URL.

This means that you should use this to reverse urls within this app.

reverse_appindexurl(args=None, kwargs=None)

Shortcut for:

reverse_appurl(crapp.INDEXVIEW_NAME, args=args, kwargs=kwargs)
classmethod get_appurls()

Get app URLs. Defaults to appurls.

Returns:class:.Url objects.
Return type:A list of
classmethod build_urls(cradmin_instance_id, appname)

Used internally by django_cradmin.crinstance.BaseCrAdminInstance.urls() to build urls for all views in the app.

CRInstance

reverse_cradmin_url(instanceid, appname=None, roleid=None, viewname='INDEX', args=None, kwargs=None)

Reverse an URL within a cradmin instance.

Usage is very similar to django.core.urlresolvers.reverse(), but you specify the cradmin instance, appname, roleid and viewname instead of the url-name

Examples

Reverse the frontpage on an app:

myapp_index_url = reverse_cradmin_url(
    instanceid='siteadmin',
    appname='myapp',
    roleid=site.id)

Reverse a specific view within an app:

myapp_add_url = reverse_cradmin_url(
    instanceid='siteadmin',
    appname='myapp',
    roleid=site.id,
    viewname='add')
class BaseCrAdminInstance(request)

Bases: object

Base class for a django_cradmin instance.

You define a subclass of this to setup a django_cradmin instance.

request

HttpRequest

The current HttpRequest.

Parameters:request (HttpRequest) – The current HttpRequest. Stored in request.
id = None

The ID of the cradmin instance. Must be unique for the Django instance/site. Must be a string. This is typically a short readable slug that describes what the cradmin instance does. You do not need to specify this except you need to communicate/link between cradmin instances.

roleid_regex = '\\d+'

The regex for matching the role id. Defaults to \d+.

main_menu_renderable_class

The renderable class for the main menu. See get_main_menu_renderable().

alias of DefaultMainMenuRenderable

expandable_menu_renderable_class

The renderable class for the expandable menu (the menu that is toggled by a button in the main menu). See get_expandable_menu_renderable().

alias of DefaultExpandableMenuRenderable

header_renderable_class

The header class for this cradmin instance. Must be a subclass of django_cradmin.crheader.AbstractHeaderRenderable.

alias of DefaultHeaderRenderable

footer_renderable_class = None

The footer class for this cradmin instance. Must be a subclass of django_cradmin.crfooter.AbstractFooter.

roleclass = None

The class defining the role for this cradmin instance. If you do not set this, the role system will not be used, which means that you will not get a value in request.cradmin_role.

rolefrontpage_appname = None

The name of the app that the user should be redirected to after selecting a role. Subclasses MUST eighter specify this or override rolefrontpage_url().

flatten_rolefrontpage_url = False

If this is True, we do not prefix the urls of the rolefrontpage_appname with the appname. This means that it is hosted on /baseurl/<roleid>/ instead of /baseurl/<roleid>/<appname>/.

If you couple this with setting roleclass to None, the frontpage will be hosted directly on /baseurl/.

If you set this to True, you have to be ensure that the urls of any views within the rolefrontpage app does crash any urls in any of the other apps.

apps = []

Apps within the instance. Iterable of (appname, appclass) tuples where appname is a slug for the app and appclass is a subclass of django_cradmin.crapp.App. Can also be specified by overriding get_apps().

get_cradmin_theme_path()

Return a path to a theme in the same format as DJANGO_CRADMIN_THEME_PATH, to use a custom theme for this instance.

get_rolequeryset()

Get the roles for the authenticated user.

You get the authenticated user from self.request.user.

get_titletext_for_role(role)

Get a short title briefly describing the given role.

get_descriptiontext_for_role(role)

Get a longer description for the given role.

This is never used directly on its own - it is just the default text used by get_descriptionhtml_for_role(). If you want HTML in your description, override get_descriptionhtml_for_role() instead.

get_descriptionhtml_for_role(role)

Get a longer description for the given role. This is always shown after/below get_titletext_for_role().

Defaults to get_descriptiontext_for_role() filtered to make it HTML safe and wrapped in a paragraph tag.

get_roleid(role)

Get the ID for the given role.

get_role_from_roleid(roleid)

Get the role for the given roleid.

Defaults to looking up a roleclass object where pk==roleid.

Returns:A role object or None.
invalid_roleid_response(roleid)

This is called whenever someone requests a role slug that does not exist (if :meth:.`get_role_from_roleid`) returns None.

Returns:Defaults to rendering django_cradmin/invalid_roleid.django.html.
Return type:django.http.HttpResponse
get_role_from_rolequeryset(role)

Returns the given role extracted via the get_rolequeryset() queryset.

Raises ObjectDoesNotExist if the role is not found in the queryset.

missing_role_response(role)

This is called whenever someone requests a role that exists but that they do not have (where meth:.get_role_from_rolequeryset raises DoesNotExist).

Returns:Defaults to rendering django_cradmin/missing_role.django.html
Return type:django.http.HttpResponse
get_main_menu_renderable()

Get the main menu renderable instance.

Defaults to a instance of the class specified in main_menu_renderable_class.

Returns:An AbstractMenuRenderable object.
Return type:django_cradmin.crmenu.AbstractMenuRenderable
get_expandable_menu_renderable()

Get the expandable menu renderable instance. This is the menu that is expanded by a button which by default is in the main menu.

Defaults to a instance of the class specified in expandable_menu_renderable_class.

Returns:An AbstractMenuRenderable object.
Return type:django_cradmin.crmenu.AbstractMenuRenderable
get_header_renderable(headername='default')

Get the header renderable for this cradmin instance.

Defaults to a instance of the class specified in header_renderable_class.

Returns:An AbstractHeaderRenderable object.
Return type:django_cradmin.crheader.AbstractHeaderRenderable

See also

django_cradmin.crheader.AbstractHeaderRenderable.

Get the footer renderable for this cradmin instance.

Defaults to a instance of the class specified in footer_renderable_class.

Returns:An AbstractHeaderRenderable object.
Return type:django_cradmin.crfooter.AbstractHeaderRenderable

See also

django_cradmin.crfooter.AbstractHeaderRenderable.

reverse_url(appname, viewname, args=None, kwargs=None, roleid=None)

Reverse an URL within this cradmin instance.

The advantage over using django.core.urlresolvers.reverse() is that you do not need to hardcode the id of the cradmin instance, and that the roleid is automatically added to args or kwargs (depending on which one you use to pass arguments to the url).

Parameters:
  • appname (str) – The name of the app.
  • viewname (str) – The name of the view within the app.
  • args (list) – Args for the view
  • kwargs (dict) – Keyword args for the view.
  • roleid – The roleid. Defaults to the ID of the current role (or None if there is no current role).
appindex_url(appname, args=None, kwargs=None, roleid=None)

Reverse the url of the landing page for the given app.

The landing page is the view named django_cradmin.crapp.INDEXVIEW_NAME.

This would be the same as using reverse_url() with viewname=crapp.INDEXVIEW_NAME.

Parameters:
  • appname (str) – The name of the app.
  • args (list) – Args for the view
  • kwargs (dict) – Keyword args for the view.
  • roleid – The roleid. Defaults to the ID of the current role (or None if there is no current role).
rolefrontpage_url(roleid=None)

Returns the URL that the user should be redirected to after selecting a role.

get_instance_frontpage_url()

Return the URL of the instance frontpage view.

See:
get_instance_frontpage_url().
roleselectview_url()

Deprecated, use get_instance_frontpage_url() instead.

get_common_http_headers()

Override this to set common HTTP headers for all views in the instance.

Returns:A mapping object mapping HTTP header name to value. Returns empty dict by default.
has_access()

Check if the given user has access to this cradmin instance.

Defaults to self.request.user.is_authenticated(), but you can override this.

get_two_factor_auth_viewname()

Get the two-factor authentication view specified in settings with DJANGO_CRADMIN_TWO_FACTOR_AUTH_VIEWNAME

Returns:The viewname if specified in settings, else it returns None.
get_foreignkeyselectview_url(model_class)

Get foreign key select view URL for the given model class.

This can be used by foreign key select widgets to lookup a view for this model within the current instance.

By default this returns None, so you have to override this if you want to use it.

Parameters:model_class – A django.db.models.Model subclass.
get_manytomanyselectview_url(model_class)

Get many-to-many select view URL for the given model class.

This can be used by many-to-many widgets, like django_cradmin.widgets.modelmultichoice.ModelMultiChoiceWidget, to lookup a view for this model within the current instance.

By default this returns None, so you have to override this if you want to use it.

Parameters:model_class – A django.db.models.Model subclass.
classmethod get_roleselect_viewclass()

Get the viewclass for the roleselect view.

See get_roleselect_view().

Returns:Defaults to django_cradmin.views.roleselect.RoleSelectView, but any subclass of django.views.View can be used.
Return type:django.views.View
classmethod get_roleselect_view()

Get the view for selecting role.

Instanciates the view class returned by get_roleselect_viewclass(), and decorates the view with django_cradmin.decorators.has_access_to_cradmin_instance().

You should not need to override this, override get_roleselect_viewclass() instead.

Note

The name of the URL for this view is <cradmin instance id>-roleselect, where <cradmin instance id> is id. You can reverse the URL of this view with get_instance_frontpage_url().

classmethod get_apps()

See apps. Defaults to returning apps, but can be overridden.

classmethod urls()

Get the url patterns for the cradmin instance.

add_extra_instance_variables_to_request(request)

Override this method to add extra attributes to the request object for all views in this cradmin instance.

This is called by the decorator that wraps all views within the instance.

get_body_css_classes_list()

Get the css classes for the <body> element that this cradmin instance should add for all views as a list.

Returns an empty list by default, but you should override this to add css classes to the <body> element for all views within this instance.

get_body_css_classes_string()

Get the css classes for the <body> element that this cradmin instance should add for all views as a string.

You should not override this - override get_body_css_classes_list(). This method only joins the list returned by that method to make it easier to use in Django templates.

page_cover_bem_block

Get the name of the BEM block for the page cover.

Should be overridden if you do not want the default of adminui-page-cover.

If you need more complex behavior, you should consider: - Making your own templates that extend:

  • django_cradmin/standalone-base.django.html - for all the views outside the role.
  • django_cradmin/base.django.html - for all the views within the role
  • OR override (this affects all cradmin instances):
    • django_cradmin/standalone-base.django.html
    • django_cradmin/base.django.html
get_default_javascriptregistry_component_ids()

Get default component IDs for all views within this cradmin instance.

Defaults to ['django_cradmin_javascript'].

get_default_within_role_javascriptregistry_component_ids()

Get default component IDs for all views within a cradmin_role within this cradmin instance.

Defaults to get_default_javascriptregistry_component_ids().

class NoRoleMixin

Bases: object

Mixin to make a BaseCrAdminInstance not require a role.

Must be mixed in before BaseCrAdminInstance.

class NoLoginMixin

Bases: object

Mixin to make a BaseCrAdminInstance not require login.

Must be mixed in before BaseCrAdminInstance.

has_access()

We give any user access to this instance, including unauthenticated users.

class NoRoleNoLoginCrAdminInstance(request)

Bases: django_cradmin.crinstance.NoRoleMixin, django_cradmin.crinstance.NoLoginMixin, django_cradmin.crinstance.BaseCrAdminInstance

Shortcut for creating a BaseCrAdminInstance with the
NoRoleMixin and NoLoginMixin.
Parameters:request (HttpRequest) – The current HttpRequest. Stored in request.

Cradmin menu system

Using crmenu with BaseCrAdminInstance

Simple example

Lets create a menu with two links, one to the “dashboard” app, and one to the “pages” app (both apps are within the cradmin instance):

class CrInstance(crinstance.BaseCrAdminInstance):
    # ... other required BaseCrAdminInstance attributes and methods ...

     apps = [
        ('dashboard', dashboard.App),
        ('dashboard', pages.App),
     ]

    def get_menu_item_renderables(self):
        return [
            crmenu.LinkItemRenderable(
                label=ugettext_lazy('Dashboard'),
                url=self.appindex_url('dashboard'),
                is_active=self.request.cradmin_app.appname == 'dashboard'),
            crmenu.LinkItemRenderable(
                label=ugettext_lazy('Pages'),
                url=self.appindex_url('pages'),
                is_active=self.request.cradmin_app.appname == 'pages'),
        ]
How it all fits together

The menu is split into a main menu and an expandable menu. Both the main and expandable menu are subclasses of django_cradmin.crmenu.AbstractMenuRenderable.

By default the main menu and the expandable menu get their menu items from django_cradmin.crinstance.BaseCrAdminInstance.get_menu_item_renderables(), but you can make them have different items by overriding:

  • get_main_menu_item_renderables()
  • get_expandable_menu_item_renderables()

If you want to change how the menu is rendered, you can change the menu renderer classes by overring the following attributes:

If changing the renderer classes is not enough, you can override the methods that creates renderable objects:

The last option gives you full control, and only require you to return an django_cradmin.renderable.AbstractRenderable object.

Implementation details

A menu is just a django_cradmin.renderable.AbstractRenderable, and with some overrides of methods in django_cradmin.crinstance.BaseCrAdminInstance, you can even use any direct subclass of AbstractRenderable. For most cases you will want to use a subclass of django_cradmin.crmenu.AbstractMenuRenderable for your menu.

Since we just use the AbstractRenderable framework, it is really easy to use a plain Django template for your menu. This is nice when working with complex menus.

API

class NavLinkItemRenderable(label, url, is_active=False, bem_variant_list=None, parent_bem_block=None)

Bases: django_cradmin.crmenu.BaseMenuLinkRenderable

Use this to add links to the main menu.

Parameters:
  • label – A label shown in the menu.
  • url – The url to go to whem the user clicks the menu item.
  • is_active – Should be True if the menuitem should be styled as active.
class NavLinkButtonItemRenderable(label, url, is_active=False, bem_variant_list=None, parent_bem_block=None)

Bases: django_cradmin.crmenu.NavLinkItemRenderable

Use this to add links styled as a button to the main menu.

Parameters:
  • label – A label shown in the menu.
  • url – The url to go to whem the user clicks the menu item.
  • is_active – Should be True if the menuitem should be styled as active.
class ExpandableMenuItem(label, url, is_active=False, bem_variant_list=None, parent_bem_block=None)

Bases: django_cradmin.crmenu.BaseMenuLinkRenderable

Use this to add links to the main menu.

Parameters:
  • label – A label shown in the menu.
  • url – The url to go to whem the user clicks the menu item.
  • is_active – Should be True if the menuitem should be styled as active.
class MenuToggleItemItemRenderable(parent_bem_block=None, **kwargs)

Bases: django_cradmin.renderable.AbstractBemRenderable

Use this to add an expandable menu toggle to the menu.

class AbstractMenuRenderable(request, cradmin_instance=None)

Bases: django_cradmin.renderable.AbstractRenderableWithCss

Base class for rendering a menu.

To get a completely custom HTML menu, you simply set your own template (see django_cradmin.renderable.AbstractRenderable.get_template_name()) and write your own HTML.

The link renderable class used by make_link_renderable().

button_renderable_class = None

The button renderable class used by make_button_renderable().

iter_renderables()

Get an iterator over the items in the list.

Get the link renderable class.

The default implementation returns link_renderable_class, and raises ValueError if it is None.

You can override this method, or override link_renderable_class to change the link renderable class.

get_button_renderable_class()

Get the button renderable class.

The default implementation returns button_renderable_class, and raises ValueError if it is None.

You can override this method, or override button_renderable_class to change the button renderable class.

make_child_renderable_kwargs(**kwargs)

Takes the user provided kwargs for make_link_renderable() or make_button_renderable() and returns the final kwargs that should be used as argument for the constructor of the renderable class.

By default, this adds the parent_bem_block kwarg with the value returned by get_child_renderable_parent_bem_block().

Make a link renderable.

Uses the renderable returned by get_link_renderable_class(), and forwards **kwargs to the constructor of that class.

Kwargs is filtered through make_child_renderable_kwargs(), so you can override that method to add default kwargs.

Returns:A link renderable object.
make_button_renderable(**kwargs)

Make a button renderable.

Uses the renderable returned by get_button_renderable_class(), and forwards **kwargs to the constructor of that class.

Kwargs is filtered through make_child_renderable_kwargs(), so you can override that method to add default kwargs.

Returns:A button renderable object.

Uses make_link_renderable() to create a renderable, and appends the created link to the menu.

append_button(**kwargs)

Uses make_button_renderable() to create a renderable, and appends the created button to the menu.

Uses make_link_renderable() to create a renderable, and prepends the created link to the menu.

prepend_button(**kwargs)

Uses make_button_renderable() to create a renderable, and prepends the created button to the menu.

Uses make_link_renderable() to create a renderable, and inserts the created link in the menu.

insert_button(index, **kwargs)

Uses make_button_renderable() to create a renderable, and inserts the created button in the menu.

get_wrapper_htmltag()

Get the HTML tag to wrap the menu in.

Defaults to "div".

get_wrapper_htmltag_id()

Get the ID of the wrapper HTML element.

has_items()
Returns:True if the list has any items, and False otherwise.
Return type:bool
insert(index, renderable_object)

Insert a renderable object at a specific index in the menu.

Parameters:
prepend(renderable_object)

Prepend a renderable object to the menu.

Parameters:renderable_object – The renderable object (a subclass of django_cradmin.renderable.AbstractRenderable)
append(renderable_object)

Append a renderable object to the menu.

Parameters:renderable_object – The renderable object (a subclass of django_cradmin.renderable.AbstractRenderable)
extend(renerable_iterable)

Just like append() except that it takes an iterable of renderables instead of a single renderable.

class DefaultMainMenuRenderable(request, cradmin_instance=None)

Bases: django_cradmin.crmenu.AbstractMenuRenderable

The default large screen (desktop) Menu renderable.

menutoggle_renderable_class

alias of MenuToggleItemItemRenderable

alias of NavLinkItemRenderable

button_renderable_class

alias of NavLinkButtonItemRenderable

class DefaultExpandableMenuRenderable(request, cradmin_instance=None)

Bases: django_cradmin.crmenu.AbstractMenuRenderable

The default small screen (mobile) Menu renderable.

alias of ExpandableMenuItem

Settings

DJANGO_CRADMIN_THEME_PATH

The staticfiles path to the theme CSS. If this is not set, we use django_cradmin/dist/css/cradmin_theme_default/theme.css.

DJANGO_CRADMIN_CSS_ICON_MAP

A dictionary mapping generalized icon names to css classes. It is used by the cradmin_icon template tag. If you do not set this, you will get font-awesome icons as defined in django_cradmin.css_icon_map.FONT_AWESOME.

See also

cradmin_icon_tags and issue 43.

DJANGO_CRADMIN_CSS_ICON_LIBRARY_PATH

The staticfiles path to the css icon library. Defaults to "django_cradmin/dist/vendor/fonts/fontawesome/css/font-awesome.min.css".

DJANGO_CRADMIN_MENU_SCROLL_TOP_FIXED

If this is True, the menu template will add an angularjs directive that automatically scrolls the menu when the window is scrolled.

DJANGO_CRADMIN_HIDE_PAGE_HEADER

If this is True, we do not render the page header. This only affects views that use templates inheriting from the django_cradmin/standalone-base.django.html template. This means all the views in django_cradmin.viewhelpers, but not the login views, or other standalone (non-crapp.App views).

DJANGO_CRADMIN_INCLUDE_TEST_CSS_CLASSES

Enable CSS classes for unit tests? The CSS classes added using the django_cradmin.templatetags.cradmin_tags.cradmin_test_css_class() template tag is not included unless this is True. Should only be True when running automatic tests. Defaults to False.

DJANGO_CRADMIN_DEFAULT_HEADER_CLASS

The default header class rendered in the header block of the base template for all cradmin view templates (standalone-base-internal.django.html). If this is None, or not specified (the default), we do not render a header.

Must be the string path to a subclass of django_cradmin.crheader.AbstractHeaderRenderable.

DJANGO_CRADMIN_DEFAULT_EXPANDABLE_CLASS

The default expandable menu class rendered at the end of <body> by the base template for all cradmin view templates (standalone-base-internal.django.html). If this is None, or not specified (the default), we do not render an expandable menu.

Must be the string path to a subclass of django_cradmin.crmenu.AbstractMenuRenderable.

DJANGO_CRADMIN_DEFAULT_STATIC_COMPONENT_IDS

List of static components registered with the django_cradmin.javascriptregistry.registry.Registry singleton that should be available by default in all templates extending the standalone-base-internal.django.html template unless something else is specified by the view or cradmin instance.

uicontainer

DJANGO_CRADMIN_UICONTAINER_VALIDATE_BEM

Set this to False in production to disable validation of BEM blocks and elements. See django_cradmin.uicontainer.container.AbstractContainerRenderable.should_validate_bem(). for more details.

DJANGO_CRADMIN_UICONTAINER_VALIDATE_DOM_ID

Set this to False in production to disable validation of DOM ids. See django_cradmin.uicontainer.container.AbstractContainerRenderable.should_validate_dom_id(). for more details.

Template tags

cradmin_tags

cradmin_titletext_for_role(context, role)

Template tag implementation of django_cradmin.crinstance.BaseCrAdminInstance.get_titletext_for_role().

cradmin_descriptionhtml_for_role(context, role)

Template tag implementation of django_cradmin.crinstance.BaseCrAdminInstance.get_titletext_for_role().

cradmin_rolefrontpage_url(context, role)

Template tag implementation of django_cradmin.crinstance.BaseCrAdminInstance.rolefrontpage_url().

cradmin_appurl(context, viewname, *args, **kwargs)

Template tag implementation of django_cradmin.crapp.App.reverse_appurl().

Examples

Reverse the view named "edit" within the current app:

{% load cradmin_tags %}

<a href='{% cradmin_appurl "edit" %}'>Edit</a>

Reverse a view with keyword arguments:

{% load cradmin_tags %}

<a href='{% cradmin_appurl "list" mode="advanced" orderby="name" %}'>
    Show advanced listing ordered by name
</a>
cradmin_appindex_url(context, *args, **kwargs)

Template tag implementation of django_cradmin.crinstance.BaseCrAdminInstance.appindex_url().

Examples

Reverse index (frontpage) of current app:

{% load cradmin_tags %}

<a href='{% cradmin_appindex_url %}'>
    Go to pages-app
</a>

Reverse a view with keyword arguments:

{% load cradmin_tags %}

<a href='{% cradmin_appindex_url mode="advanced" orderby="name" %}'>
    Show advanced listing ordered by name
</a>
cradmin_instance_appindex_url(context, appname, *args, **kwargs)

Template tag implementation of django_cradmin.crinstance.BaseCrAdminInstance.appindex_url().

Examples

Reverse index (frontpage) of the "pages" app:

{% load cradmin_tags %}

<a href='{% cradmin_instance_appindex_url appname="pages" %}'>
    Go to pages-app
</a>

Reverse a view with keyword arguments:

{% load cradmin_tags %}

<a href='{% cradmin_instance_appindex_url appname="pages" mode="advanced" orderby="name" %}'>
    Show advanced listing ordered by name
</a>
cradmin_instance_url(context, appname, viewname, *args, **kwargs)

Template tag implementation of django_cradmin.crinstance.BaseCrAdminInstance.reverse_url().

Examples

Reverse the view named "edit" within the app named "pages":

{% load cradmin_tags %}

<a href='{% cradmin_instance_url appname="pages" viewname="edit" %}'>
    Edit
</a>

Reverse a view with keyword arguments:

{% load cradmin_tags %}

<a href='{% cradmin_instance_url appname="pages" viewname="list" mode="advanced" orderby="name" %}'>
    Show advanced pages listing ordered by name
</a>
cradmin_instanceroot_url(instanceid)

Get the URL of the cradmin instance with the provided instanceid.

Parameters:instanceid – The id if a django_cradmin.crinstance.BaseCrAdminInstance.
cradmin_url(context, instanceid=None, appname=None, roleid=None, viewname='INDEX', *args, **kwargs)

Template tag implementation of django_cradmin.crinstance.reverse_cradmin_url().

Examples

Reverse the view named "edit" within the app named "pages" in the cradmin-instance with id "my_cradmin_instance" using roleid 10:

{% load cradmin_tags %}

<a href='{% cradmin_url instanceid="my_cradmin_instance" appname="pages"
roleid=10 viewname="edit" %}'>
    Edit
</a>

Reverse a view with keyword arguments:

{% load cradmin_tags %}

<a href='{% cradmin_url instanceid="my_cradmin_instance" appname="pages"
roleid=10 viewname="list" mode="advanced" orderby="name" %}'>
    Show advanced pages listing ordered by name
</a>
cradmin_jsonencode(json_serializable_pythonobject)

Template filter that converts a json serializable object to a json encoded string.

cradmin_jsonencode_html_attribute_value(json_serializable_pythonobject)

Template tag that converts a json serializable object to a json encoded string and quotes it for use as an attribute value.

Examples

Typical usage:

{% load cradmin_tags %}

<div data-something={% cradmin_jsonencode_html_attribute_value serializableobject %}></div>

Notice that we do not add " around the value - that is done automatically.

cradmin_theme_staticpath(context)
cradmin_render_renderable(context, renderable, include_context=False, **kwargs)

Render a django_cradmin.renderable.AbstractRenderable.

Unlike just using {{ renderable.render }}, this sends the request into render (so this is the same as calling renderable.render(request=context['request']).

Examples

Render a renderable named renderable in the current template context:

{% load cradmin_tags %}

{% cradmin_render_renderable renderable %}
cradmin_test_css_class(suffix)

Adds a CSS class for automatic tests. CSS classes added using this template tag is only included when the the DJANGO_CRADMIN_INCLUDE_TEST_CSS_CLASSES setting is set to True.

To use this template tag, you provide a suffix as input, and the output will be `` test-<suffix> ``. Notice that we include space before and after the css class - this means that you do not need to add any extra spaces within your class-attribute to make room for the automatic test only css class.

Examples

Use the template tag to add test only css classes:

{% load cradmin_tags %}

<p class="paragraph paragraph--large{% cradmin_test_css_class 'introduction' %}">
    The introduction
</p>

Ensure that your test settings have DJANGO_CRADMIN_INCLUDE_TEST_CSS_CLASSES = True.

Write tests based on the test css class:

from django import test
import htmls

class TestCase(test.TestCase):

    def test_introduction(self):
        response = some_code_to_get_response()
        selector = htmls.S(response.content)
        with self.assertEqual(
            'The introduction',
            selector.one('test-introduction')
Parameters:suffix – The suffix for your css class. The actual css class will be `` test-<suffix> ``.
cradmin_join_css_classes_list(css_classes_list)

Joins a list of css classes.

Parameters:css_classes_list (list) – List or other iterable of css class strings.

Examples

Simple example:

{% cradmin_join_css_classes_list my_list_of_css_classes %}
cradmin_render_header(context, headername='default', include_context=True, **kwargs)

Render a header.

Parameters:

Examples

Render the default header:

{% cradmin_render_header %}
... or ...
{% cradmin_render_header headername='default' %}

Render a custom header:

{% cradmin_render_header headername='myheader' %}

The last example assumes that you have overridden django_cradmin.crinstance.BaseCrAdminInstance.get_header_renderable() to handle this headername as an argument.

cradmin_render_default_header(context)

Render the default header specified via the :setting:DJANGO_CRADMIN_DEFAULT_HEADER_CLASS` setting.

cradmin_render_default_expandable_menu(context)

Render the default header specified via the :setting:DJANGO_CRADMIN_DEFAULT_EXPANDABLE_CLASS` setting.

cradmin_theme_static(context, path, absolute=False)

Works just like the {% static %} template tag, except that it expects path to be relative to the path specified in the DJANGO_CRADMIN_THEME_PREFIX setting.

Parameters:
  • path (str) – The path to lookup.
  • absolute (bool) – Should we create an absolute url including the domain? Defaults to False.

cradmin_icon_tags

The cradmin_icon_tags Django template tag library defines tags that makes it easy to swap out the icons used by the provided Django cradmin components.

It is used like this:

{% load cradmin_icon_tags %}

<span class="{% cradmin_icon 'search' %}"></span>

where {% cradmin_icon 'search' %} will look up css classes for the icon in the DJANGO_CRADMIN_CSS_ICON_MAP Django setting. If DJANGO_CRADMIN_CSS_ICON_MAP is not set, we default to django_cradmin.css_icon_map.FONT_AWESOME, but you can easily provide your own with something like this in your settings.py:

from django_cradmin import css_icon_map
DJANGO_CRADMIN_CSS_ICON_MAP = css_icon_map.FONT_AWESOME.copy()
DJANGO_CRADMIN_CSS_ICON_MAP.update({
    'search': 'my my-search-icon'
})

You can even add your own icons and use cradmin_icon for your own views/components.

FONT_AWESOME = {'x': 'fa fa-times', 'list-ul': 'fa fa-list-ul', 'loadspinner': 'fa fa-spin fa-spinner', 'italic': 'fa fa-italic', 'bold': 'fa fa-bold', 'pager-next-page': 'fa fa-chevron-right', 'h1': 'fa fa-header django-cradmin-icon-font-lg', 'close-overlay-right-to-left': 'fa fa-chevron-left', 'search': 'fa fa-search', 'h3': 'fa fa-header django-cradmin-icon-font-sm', 'list-ol': 'fa fa-list-ol', 'pager-previus-page': 'fa fa-chevron-left', 'select-right': 'fa fa-angle-left', 'caret-up': 'fa fa-caret-up', 'caret-down': 'fa fa-caret-down', 'h2': 'fa fa-header', 'codeblock': 'fa fa-code', 'link': 'fa fa-link', 'select-left': 'fa fa-angle-right'}

Font-awesome icon map for the cradmin_icon template tag.

Creating a custom theme

Getting started

TODO

SASS style guide

  • Indent with 4 spaces.
  • Use BEM naming syntax. See http://getbem.com/ and http://cssguidelin.es/#bem-like-naming.
  • Document everything with PythonKSS.
  • Use md, lg, xl, ... (as part of modifier name) for breakpoints.
  • Use small, large, xlarge, ... (as part of modifier name) for sizes.
  • Never use @extend to extend a component. Components should be as isolated as possible. They may require another component to be useful, but they should not extend another component.

PythonKSS documentation style guide

Never use numbers for section references

Use <setting|generic|base|comonent>.<BEM-block>.

E.g: component.modal.

Define dependencies last in the description

Define dependencies last in the description as follows:

/* Something

Some description.

# Depends on
- component.modal
- component.backdrop

Styleguide something.something
*/

HTML style guide

  • Never use ID for styling.
  • Prefix IDs with id_, and write ids in all lowercase with words separated by a single _. Example: id_my_cool_element.
  • Separate css classes with two spaces. Example: <div class="class1  class2">

Icons

How it works

Icon names are virtual (icon package agnostic). The default icon names are defined in:

django_cradmin/apps/django_cradmin_styles/staticsources/django_cradmin_styles/styles/basetheme/1__settings/_cricon.scss

When adding support for an icon package (font-awesome, ionicons, ...), we need to implement a set of mixins, and import those mixins before we import basetheme/3__base/all. We supply an implementation for font-awesome by default. If you just want to use fon

Extending the default font-awesome icon set

This is fairly easy. You just need to add mapping from a virtual name to a font-awesome variable for the icon in $cricon-font-awesome-free-icon-map, and add the virtual names to $cricon-extra-icon-names.

Example - adding the align right and align center icons from font-awesome:

@import 'basetheme/3__base/cricon/cricon-font-awesome';
$cricon-font-awesome-free-icon-map: map-merge($cricon-font-awesome-free-icon-map, (
    align-center: $fa-var-align-center,
    align-right: $fa-var-align-right
));
$cricon-extra-icon-names: align-center, align-right;
@import 'basetheme/3__base/all';

With this, cricon--align-center and cricon--align-right css classes will be available.

Adding support for another icon set

Take a look at the _cricon-font-awesome.scss file - you need to implement all of the mixins from that, and import your custom icon mixins instead of _cricon-font-awesome.scss.

Releasenotes

Django cradmin 1.1.0 releasenotes

What is new?

  • Django 1.9 support.
    • Also tested with Django 1.10b1, and all tests pass, and everything seems to work as it should.
    • Minumum Django version is still 1.8.
  • Minimum version of Django-crispy-forms updated to 1.6.0.

Breaking changes

The cradmin_texformatting_tags template library was removed

It used deprecated and non-public APIs. It is not in use anywhere in the library, and was undocumented, so this should hopefully not cause any major issues for any users.

Custom themes must update their cradmin_base-folder

Because of some minor changes in form rendering in django-crispy-forms 1.6.0, all custom themes must update their cradmin_base folder to the base folder. The changes was made in commit d7b0c061e805431d01d4a48dd7def8c6ad2414ba.

Can no longer set button classes using field_classes

This is due to changes in django-crispy-forms 1.6.0. Buttons inheriting from django_cradmin.crispylayouts.CradminSubmitButton (or PrimarySubmit, DangerSubmit, DefaultSubmit, ...), must set their CSS classes using the button_css_classes attribute instead.

Django cradmin 1.1.1 releasenotes

What is new?

django_cradmin.viewhelpers can now be imported with from django_cradmin import viewhelpers. Example:

from django_cradmin import viewhelpers

class MyCreateView(viewhelpers.create.CreateView):
    pass  # more code here ...

The imported viewhelpers object does not include listbuilder, listfilter or multiselect2, they should still be imported using from django_cradmin.viewhelpers import <module>.

Breaking changes

There are no breaking changes between 1.1.0 and 1.1.1.

Django cradmin 2.0.0 releasenotes

What is new?

  • New theme. No longer based on bootstrap, and using SASS instead of LESS.
  • django_cradmin.viewhelpers can be imported as from django_cradmin import viewhelpers. After this import, you can use viewhelpers.create.CreateView, viewhelpers.update.UpdateView, ...
  • Views using any of the base templates for cradmin must inherit from one of the views in django_cradmin.viewhelpers or mix in one of the subclasses of django_cradmin.javascriptregistry.viewmixin.MinimalViewMixin.
  • New menu system. Much more flexible, with a much simpler core based on django_cradmin.viewhelpers.listbuilder.

New menu system

Menus are structurally changed, and they are defined in a different manner. Read crmenu for details.

Migrating from the old menu system

From a simple cases updating your menu for 2.x is fairly easy:

  1. Add menu items in django_cradmin.crinstance.BaseCrAdminInstance.get_menu_item_renderables() instead of in the build_menu-method of your Menu. Since the attributes of django_cradmin.crmenu.LinkItemRenderable is the same as for the old add_menuitem()-method, this is easy. Just make sure to change the active attribute to is_active.
  2. Remove your old django_cradmin.crmenu.Menu subclass.
  3. Remove the menuclass-attribute from your BaseCrAdminInstance subclass.

If your old menu was defined like this:

from django_cradmin import crinstance, crmenu

class Menu(crmenu.Menu):
    def build_menu(self):
        self.add_menuitem(
            label=_('Dashboard'), url=self.appindex_url('dashboard'),
            active=self.request.cradmin_app.appname == 'dashboard')

class CrInstance(crinstance.BaseCrAdminInstance):
    # ... other required BaseCrAdminInstance attributes and methods ...

    menuclass = Menu

It will look like this in cradmin 2.x:

class CrInstance(crinstance.BaseCrAdminInstance):
    # ... other required BaseCrAdminInstance attributes and methods ...

    def get_menu_item_renderables(self):
        return [
            crmenu.LinkItemRenderable(
                label=_('Dashboard'), url=self.appindex_url('dashboard'),
                is_active=self.request.cradmin_app.appname == 'dashboard'),
        ]

Django base template changes

standalone-base-internal.django.html
  • The div with id django_cradmin_bodycontentwrapper no longer exists, and this also means that the outside-bodycontentwrapper template block no longer exists.
  • We no longer load any javascript by default.
django_cradmin/base-internal.django.html
  • We use the new template blocks from standalone-base-internal.django.html (see the section above).
  • The pageheader and pageheader-inner blocks no longer exist. Use:
    • page-cover instead of pageheader.
    • page-cover-content instead of pageheader-inner, or use page-cover-title to just set the content of the H1 tag.

You can use the following regex in PyCharm to searh and all replace page-header-content blocks that only contain a H1 tag with the new page-cover-title block:

Text to find:

\{\% block pageheader\-inner \%\}\s*\<h1\>\s*([^>]+?)\s*\<\/h1\>\s*\{\% endblock pageheader\-inner \%\}

Replace with:

\{\% block page-cover-title \%\}\n    $1\n\{\% endblock page-cover-title \%\}

The regex in Text to find is both Python an java compatible, so you should be able to create a python script to handle this if needed.

Recommended migration route:

  • Replace pageheader-inner with the regex above.

  • Search for pageheader-inner, and update the more complex cases manually to use something like this:

    {% block page-cover-title %}
        My title
    {% endblock page-cover-title %}
    
    {% block page-cover-content %}
        {{ block.super }}
        Stuff below the title in the old pageheader-inner block.
    {% endblock page-cover-content %}
    
layouts/standalone/focused.django.html

The innerwrapper_pre and innerwrapper_post blocks no longer exists. You will typically want to update templates using these blocks with:

{% extends "django_cradmin/focused.django.html" %}

{% block body %}

    {# The content you had in innerwrapper_pre here #}

    {{ block.super }}

    {# The content you had in innerwrapper_pre here #}

{% endblock body %}

If you want the pre and post content to line up with the focused content, wrap them in section tags with the page-section page-section--tight css classes:

{% extends "django_cradmin/focused.django.html" %}

{% block body %}
    <section class="page-section page-section--tight">
        {# The content you had in innerwrapper_pre here #}
    </section>

    {{ block.super }}

    <section class="page-section page-section--tight">
        {# The content you had in innerwrapper_post here #}
    </section>
{% endblock body %}

CSS class changes

The css framework is completely new, so all CSS classes have new names and they are structured differently. This section has a

Removed css classes
  • django-cradmin-listbuilder-floatgridlist: This was never ready to use out of the box, and it is better to create this per app to make it work perfectly with whatever javascript library required to handle the layout.
Listbuilder lists

Listbuilder lists use the new list css class. Unlike the old django-cradmin-listbuilder-list css class, this does not override typography styles. Instead it only focus on layout-specific styles.

This means that you need to use css classes to style heading elements unless you want them to have their original sizes.

Deprecated in the python library

  • django_cradmin.crmenu.MenuItem.get_active_item_wrapper_tag is deprecated. Use django_cradmin.crmenu.MenuItem.get_menu_item_active_htmltag().

Removed from the python library

  • django_cradmin.viewhelpers.listbuilder.lists.FloatGridList is removed for the reason explained for the django-cradmin-listbuilder-floatgridlist css class above.

Changes in the template tags

  • The django_cradmin.templatetags.cradmin_tags.cradmin_theme_staticpath template tag raises an exception if request is not in the template context.

Django cradmin 3.0.0 releasenotes

Note

This release marks the end of long lasting major releases. From now on we will release a major release when adding anything major, or when introducing any changes that require some kind of manual or dangerous update of existing projects.

Whats new

  • Add a new set of icon mixins and standard icons. Font-awesome is implemented as the default, but adding other icon sets is fairly easy. This is documented in Creating a custom theme.
  • Update the javascript with the new icon css classes.

Note

There is no changes to the Python code, only so the theme .scss and javascript files.

Migrate from 2.x to 3.x

Update .scss files

After updating to 3.x, your custom themes will break. You will need to update so that you include @import 'basetheme/3__base/cricon/cricon-font-awesome'; before @import 'basetheme/3__base/all';.

So if you had the following with cradmin 2.x:

@import 'basetheme/1__settings/all';
@import '1__settings/all';

@import 'basetheme/2__generic/all';
@import '2__generic/all';

@import 'basetheme/3__base/all';
@import '3__base/all';

@import 'basetheme/4__components/all';
@import '4__components/all';

You will have the following with 3.x+:

@import 'autogenerated_partials/variables';
@import 'basetheme/1__settings/all';
@import '1__settings/all';

@import 'basetheme/2__generic/all';
@import '2__generic/all';

@import 'basetheme/3__base/cricon/cricon-font-awesome';  // This line is new!
@import 'basetheme/3__base/all';
@import '3__base/all';

@import 'basetheme/4__components/all';
@import '4__components/all';

Furthermore, if you have a custom $font-size variable you need to ensure you have all the required sizes in the map:

  • xxsmall
  • xsmall
  • small
  • medium
  • large
  • xlarge
  • xxlarge
  • xxxlarge
  • smallcaps
Update javascript

You need to update your package.json to have “django_cradmin_js”: “^3.0.0” as a requirement.

Django cradmin 4.0.0 releasenotes

What is new?

  • Cleanup of most of the leftovers from cradmin 1.x
  • cradmin_legacy compatibility. The cradmin_legacy library is a fork of the django_cradmin 1.x branch, which can be used alongside django_cradmin >= 4.0.0.

Migrate from 3.x to 4.x

Update for ievv buildstatic builds

If you use ievv buildstatic to build your sass, you need to update to ievv-opensource >= 4.5.0. You can, and should:

  • update your ievvbuildstatic.sassbuild.Plugin in the IEVVTASKS_BUILDSTATIC_APPS setting to cradmin_ievvbuildstatic.SassBuild. You will also need to add from django_cradmin.utils import cradmin_ievvbuildstatic.

  • Remove:

    ievvbuildstatic.mediacopy.Plugin(
        sourcefolder=ievvbuildstatic.filepath.SourcePath('django_cradmin_styles', 'media'),
        destinationfolder='media'
    ),
    
  • Add @import 'autogenerated_partials/variables'; to the top of your main.scss, or whatever your main scss source file is.

The reason why you do not need to mediacopy the “media” directory is that cradmin_ievvbuildstatic.SassBuild sets the sass variable $media-path to point to the files from django_cradmin_styles. The $media-path variable is imported from autogenerated_partials/variables.

Deprecated apps, and how to handle the deprecation

Deprecated: cradmin_imagearchive

The django_cradmin.apps.cradmin_imagearchive app has been deprecated and moved to django_cradmin.deprecated_apps.cradmin_imagearchive`, and the database model has been renamed from ``ArchiveImage to ArchiveImageDeprecated.

The cradmin_imagearchive module should have been removed in django_cradmin 2.0.0, but it was forgotten. If you need to use this app, you should consider:

  • Installing cradmin_legacy. Make sure you follow the guides in the cradmin_legacy for adding it to an existing django_cradmin > 2.0 install.
  • Change from django_cradmin.apps.cradmin_imagearchive -> cradmin_legacy.apps.cradmin_imagearchive in INSTALLED_APPS.

This should just work, since the appnames are the same, and their migrations match.

If you do not want to depend on cradmin_legacy, you can update your INSTALLED_APPS with django_cradmin.apps.cradmin_imagearchive -> django_cradmin.deprecated_apps.cradmin_imagearchive. When you migrate this change, the database table for ArchiveImage will be renamed so that it ends with deprecated. This avoids problems if you add cradmin_legacy in the future.

Deprecated: cradmin_temporaryfileuploadstore

The django_cradmin.apps.cradmin_temporaryfileuploadstore app has been deprecated and moved to django_cradmin.deprecated_apps.cradmin_temporaryfileuploadstore`, and the database models has been renamed from ``TemporaryFileCollection -> TemporaryFileCollectionDeprecated and TemporaryFile -> TemporaryFileDeprecated.

The cradmin_temporaryfileuploadstore module should have been removed in django_cradmin 2.0.0, but it was forgotten. If you need to use this app, you should consider:

  • Installing cradmin_legacy. Make sure you follow the guides in the cradmin_legacy for adding it to an existing django_cradmin > 2.0 install.
  • Change from django_cradmin.apps.cradmin_temporaryfileuploadstore -> cradmin_legacy.apps.cradmin_temporaryfileuploadstore in INSTALLED_APPS.

This should just work, since the appnames are the same, and their migrations match.

If you do not want to depend on cradmin_legacy, you can update your INSTALLED_APPS with django_cradmin.apps.cradmin_temporaryfileuploadstore -> django_cradmin.deprecated_apps.cradmin_temporaryfileuploadstore. When you migrate this change, the database tables will be renamed so that they end with deprecated. This avoids problems if you add cradmin_legacy in the future.

Indices and tables