django-CRadmin documentation¶
Getting started¶
Install and configure¶
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()
andTestCaseMixin.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 usingTestCaseMixin.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 formock_request()
(or any of the more specializedmock_*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 useNoLoginTestCaseMixin
(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 ondjango.test.Client
(depends on themethod
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()
andmock.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 toNone
. - htmls_selector (boolean) – If this is
True
, we create ahtmls.S()
object for the response content. Should normally not be used without also providingexpected_statuscode
because it can lead to unexpected behavior. Defaults toFalse
. - verbose (boolean) – More verbose exception messages. Useful for debugging,
but should be
False
when you are not debugging. Defaults toFalse
.
- :param httpheaders:: Forwarded to
-
-
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 usereturn 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:
- You need to inherit from
SortableBase
(an abstract model) instead ofdjango.db.Model
, - 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:
django_cradmin.sortable.models.SortableQuerySetBase.sort_before()
django_cradmin.sortable.models.SortableQuerySetBase.sort_last()
django_cradmin.sortable.models.SortableQuerySetBase.set_newitem_sort_index_to_last()
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 makessort_index
read-only by default.Used just like
django.contrib.admin.ModelAdmin
.-
make_sort_index_readonly
= False¶ If this is
True
, we make thesort_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
isTrue
.
-
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 tonow
.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
andapp
.
-
-
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 givenapp
.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 withobject_id
this creates a generic foreign key to any Django model.
-
object_id
¶ The object ID of the
content_object
. Together withcontent_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 inquerystringargs
. - ignore_none_values (bool) – If this is
True
(default), we ignoreNone
values inquerystringargs
.
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: 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
– Iftemplate_name
is not set.
-
render
(request=None, extra_context_data=None)¶ Render
get_template_names
with the context returned byget_context_data()
.- Paramteters:
- request (HttpRequest): If this is provided, we forward it to
get_context_data()
, and torender_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 toget_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()
andget_bem_variant_list()
instead.
- bem_block (str) – Get the BEM block. Can not be supplied if
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
orTB
.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
orTB
, 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
toMIDDLEWARE_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).
- Add
messages_debug_middleware — Debug django messages rendering/styles¶
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:
- Mark new translations or change existing translations.
- Build the translation files (
.po
files). - Push translation files (
.po
files) to Transifex. - Wait for translators to translate using Transifex.
- Pull translation files (
.po
files) from Transifex. - 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/
.
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.
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/
Update
django_cradmin/version.json
.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/
- Commit.
- Tag the commit with
<version>
. - Push (
git push && git push --tags
). - 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 thedjango_cradmin.crinstance.BaseCrAdminInstance.id
.
-
class
App
(appname, request, active_viewname)¶ Bases:
object
A cradmin App.
Added to a
django_cradmin.crinstance.BaseCrAdminInstance
withdjango_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 inappurls
, 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-nameExamples
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+
.
The renderable class for the main menu. See
get_main_menu_renderable()
.alias of
DefaultMainMenuRenderable
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
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 therolefrontpage_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
toNone
, 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 whereappname
is a slug for the app andappclass
is a subclass ofdjango_cradmin.crapp.App
. Can also be specified by overridingget_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, overrideget_descriptionhtml_for_role()
instead.
-
get_descriptionhtml_for_role
(role)¶ Get a longer description for the given
role
. This is always shown after/belowget_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 wherepk==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 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 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 theroleid
is automatically added to args or kwargs (depending on which one you use to pass arguments to the url).Parameters:
-
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()
withviewname=crapp.INDEXVIEW_NAME
.Parameters:
-
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.
-
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.
Returns: Defaults to django_cradmin.views.roleselect.RoleSelectView
, but any subclass ofdjango.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 withdjango_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>
isid
. You can reverse the URL of this view withget_instance_frontpage_url()
.
-
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
andNoLoginMixin
.
Parameters: request (HttpRequest) – The current HttpRequest. Stored in request
.- Shortcut for creating a
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
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_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¶
Template tag implementation of
django_cradmin.crinstance.BaseCrAdminInstance.get_titletext_for_role()
.
Template tag implementation of
django_cradmin.crinstance.BaseCrAdminInstance.get_titletext_for_role()
.
Template tag implementation of
django_cradmin.crinstance.BaseCrAdminInstance.rolefrontpage_url()
.
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>
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>
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>
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>
Get the URL of the cradmin instance with the provided
instanceid
.Parameters: instanceid – The id
if adjango_cradmin.crinstance.BaseCrAdminInstance
.
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 roleid10
:{% 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>
Template filter that converts a json serializable object to a json encoded string.
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.
Render a
django_cradmin.renderable.AbstractRenderable
.Unlike just using
{{ renderable.render }}
, this sends therequest
into render (so this is the same as callingrenderable.render(request=context['request'])
.Examples
Render a renderable named
renderable
in the current template context:{% load cradmin_tags %} {% cradmin_render_renderable renderable %}
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 toTrue
.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> ``.
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 %}
Render a header.
Parameters: - context – template context.
- headername (list) – List or other iterable of css class strings.
Sent to
django_cradmin.crinstance.BaseCrAdminInstance.get_header_renderable()
to get the header. - include_context – Forwarded to
cradmin_render_renderable()
. - **kwargs – Forwarded to
cradmin_render_renderable()
.
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.
Render the default header specified via the :setting:DJANGO_CRADMIN_DEFAULT_HEADER_CLASS` setting.
Render the default header specified via the :setting:DJANGO_CRADMIN_DEFAULT_EXPANDABLE_CLASS` setting.
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.
cradmin_email_tags¶
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 asfrom django_cradmin import viewhelpers
. After this import, you can useviewhelpers.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 ofdjango_cradmin.javascriptregistry.viewmixin.MinimalViewMixin
. - New menu system. Much more flexible, with a much simpler core based on
django_cradmin.viewhelpers.listbuilder
.
Django base template changes¶
standalone-base-internal.django.html¶
- The div with id
django_cradmin_bodycontentwrapper
no longer exists, and this also means that theoutside-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
andpageheader-inner
blocks no longer exist. Use: page-cover
instead ofpageheader
.page-cover-content
instead ofpageheader-inner
, or usepage-cover-title
to just set the content of the H1 tag.
- The
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 thedjango-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 ifrequest
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 theIEVVTASKS_BUILDSTATIC_APPS
setting tocradmin_ievvbuildstatic.SassBuild
. You will also need to addfrom 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 yourmain.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 thecradmin_legacy
for adding it to an existingdjango_cradmin > 2.0
install. - Change from
django_cradmin.apps.cradmin_imagearchive
->cradmin_legacy.apps.cradmin_imagearchive
inINSTALLED_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 thecradmin_legacy
for adding it to an existingdjango_cradmin > 2.0
install. - Change from
django_cradmin.apps.cradmin_temporaryfileuploadstore
->cradmin_legacy.apps.cradmin_temporaryfileuploadstore
inINSTALLED_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.