views:

99

answers:

4

So I'm writing a Django based website that allows users select a color scheme through an administration interface.

I already have middleware/context processors that links the current request (based on domain) to the account.

My question is how to dynamically serve the CSS with the account's custom color scheme.

I see two options:

  1. Add a CSS block to the base template that overrides the styles w/variables passed in through a context processors.

  2. Use a custom URL (e.g. "/static/dynamic/css/< website_id >/styles.css") that gets routed to a view that grabs all the necessary values and creates the css file.

I'm content with either option, but was wondering if anyone else out there has dealt with similar problems and could give some insight as to "Best Practices".

Update : I'm leaning towards option number 2, as I think this will allow for better caching down the road. So it's dynamic the first time, gets stored in memcache (or whatever), and invalidated when a user updates their settings in the admin site.

Update: Firstly, I'd like to thank everyone for their suggestions thus far. All the answers thus far have focused around generating static files. Though this would work great in production, it feels like a tremendous burden during development. If I wanted to add a new element to be styled, or tweak existing styles I'd have to go through and recreate each and every css file. Sure, this could be done with a management command, but I just don't feel it's worth it. Doing it dynamically would add 1 maybe 2 queries to each page load, which is something I'm not worried about at this stage. All I need to know is that at some point I will be able to cache it without rewriting the whole thing.

+1  A: 

Nice question.

I would suggest to pre-generate css file after colors scheme is saved. This would have positive impact on caching and overall page loading time. You can store your css files in directory /media/css/custom/<id or stometing>/styles.css or /media/css/custom/<id or sth>.css and in template add <link rel="stylesheet" href="/media/css/custom/{{some_var_pointing _to_file_name}}" />

You can also do the trick with some random number or date in css file name that could be changed each time file is saved. Thanks to this browser will load the file immediately in case of changes.

UPDATE: example of using model to improve this example To make managing of those file easy you can create simple model (one per user):

class UserCSS(models.Model):
    bg_color = models.CharField(..)
    ...
    ...

Fields (like bg_color) can represent parts of your css file. You can ovveride save method to add logic that creates css file for user (by rendering some template).

In case your file format change you can make changes in your's model definition (with some default values for new fields), make little changes in template and run save method for each exisintg instance of class. This would renew your css files.

That should work nicely.

Lukasz Dziedzia
+1  A: 

Could generate the css and store it in a textfield in the same model as the user profile/settings. Could then have a view to recreate them if you change a style. Then do your option 1 above.

+1  A: 

I would create an md5 key with the theme elements, store this key in the user profile and create a ccs file named after this md5 key : you gain static file access and automatic theme change detection.

Pierre-Jean Coudert
+1  A: 

I've used option #2 with success. There are 2 decent ways of updating the generated static files that I know of:

  1. Use a version querystring like /special_path.css?v=11452354234 where the v parameter is generated from a database field, key in memcached, or some other persistent file. Version gets updated by admin, or for development you would just make the generation not save if the parameter was something special like v=-1. You'll need a process to clean up the old generations after some time.

  2. Don't use a version querystring, but have it look first for the generated file, if it can't find it, it generates it. You can create a cron job or WSGI app that looks for filesystem changes for development, and have a hook from your admin panel that deletes generations after an update. Here's an example of the monitoring, which you would have to convert to be specific to your generations and not to Django. http://code.google.com/p/modwsgi/wiki/ReloadingSourceCode#Monitoring%5FFor%5FCode%5FChanges

Jordan
By the way, I don't recommend having the monitor running in a production app, just to be clear. That would be a performance killer.
Jordan
Thanks, I'm going to go with #1 w/ some of the tips mentioned by others.
sdolan
By the way, this method lends itself nicely to a variable replacement mechanism, since you'll be processing the .css with Python anyway.If it's not just freeform CSS, and you have a known list of properties, you could pass those into a template css file that dynamically replaces with whatever's in the database. Good luck!
Jordan
Yup, that's exactly what I've been doing. I'm creating this as a reusable app that deals with branding to manage logos, colors, templates, etc. on a domain/sub-domain basis. I'm planning to release it as open source when it becomes "functional", but this is a side project, so it may be a while. Thanks for the help and good wishes!
sdolan