Dynamic CSS in Django

August 14th, 2014

For a while I have been thinking of having my Django sites include CSS styling from a database; for instance having different themes that can be switched on and off at will. It took me a while - with some help from Django users & docs - to figure out how to do it.

My eventual solution uses a template file containing variable names where colours should go; the variables get their values generated at run-time from the database.

Designing and populating a database to manage the variables is pretty straight-forward; generating the CSS file is not a common feature it seems hence this posting.

The Template

Suppose we have a template containing CSS which we want to alter dynamically; something like this

#afc-portal-globalnav  {
  background-color: {{ globalnav_background_normal }};
  border: 1px solid {{ globalnav_border_colour }};
}

#afc-portal-globalnav ul {
  color: {{ globalnav_colour_normal }};
}

Here we have a few variable names (globalnav_background_normal, globalnav_border_colour, etc) we want to replace with colours picked from our database.

The View

We can create a Functional View which sets-up the page, queries the database, generates from the template and returns the page

In the first part, we make sure the response will be of the appropriate type. We need to do more here to make sure the page will be cached appropriately - we do not want to be generating this file for every query.

def themecss(request):
    # Create the HttpResponse object with the appropriate header.
    response = HttpResponse(content_type='text/css')

Now we can query the database and assemble the key, value pairs for the variables with their colours.

context = {}
for item in Tag.objects.all():
  context[item.tag] = item.colour

And finally we load the template with our variables and return the page.

t = loader.get_template('afc_skin_theme.css')
c = Context(context)
response.write(t.render(c))
return response

With this in place and our database populated with an appropriate structure, we can define an url to call the view and include this in our regular pages. Without any caching controls, the styling can be changed more or less immediately.

Dead easy when you know how, eh?

 

Passing CBV data to a Form

March 17th, 2013

I have just started working with Django, building an example application to find my way through the framework. Django seems to be more dynamic than I expected and the web is full of answers to questions I almost asked but help in no way at all.

I got totally stuck on this problem:

using a CBV and form_class, how can I limit a select in the form to a subset determined by a value known in the CBV

Essentially I wanted to pass a value from the View to the Form which could then limit a selector. For example, suppose we have Clients who have Contracts which have Tasks; when adding a Task for a Client we only want to see Contracts for that specific Client.

Here's the basic structure:

#models.py

class Client(models.Model):
  name = models.CharField()

class Contract(models.Model):
  client = models.ForeignKey(Client)
  name = models.Charfield)

class Task(models.Model):
  contract = models.ForeignKey(Contract)
  notes = models.TextField()

From a detail page for a Client, we want to be able to add a task to a Contract belong to the Client. We provide an URL to provide a form to add the task

#urls.py
url(r'^clients/(?P<cid>\d+)/addtask$', view=TaskCreateView.as_view()),

This will pass a value cid to the class based view (CBV) representing the id of the Client. We want the CBV to pass this value on to the form. Here's how we can do this:

#views.py
class TaskCreateView(CreateView):
  model = Task
  template_name = 'task_form.html'
  form_class = TaskForm

  def get_form_kwargs(self, **kwargs):
    cid = self.kwargs.get('cid',0)
    kwargs = super(TaskCreateView, self).get_form_kwargs(**kwargs)
    kwargs['initial']['cid'] = cid
    return kwargs

  def get_context_data(self, **kwargs):
    context = super(TaskCreateView, self).get_context_data(**kwargs)
    if 'cid' in self.kwargs:
      context['client'] = get_object_or_404(Client, id=context['cid'])
    return context

Here the get_form_kwargs picks out cid and passes it to the form as a keyword argument. In get_context_data we also pick out cid this time fetching the Client record and returning it in the context so the template can render appropriate information about the Client in the Task form.

The Contract choices can now be specified in the form:

#forms.py
class TaskForm(forms.ModelForm):
  class Meta:
    model = Task

  def __init__(self, *args, **kwargs):
    super(TaskForm, self).__init__(*args, **kwargs)
    cid = kwargs['initial'].get('cid',0)
    if cid:
      self.fields['contract'].choices = ((c.id, c.name) for c in Contract.objects.filter(client=cid))

Here, as the form is being initialised, if a cid value has been passed from the View, we can filter the list of Contracts in the select widget

 
 
© 2013 Andy Ferguson