Saturday, 31 August 2013

Serving Static Files in Django

This post shows the steps for setting up static files in Django.   Static files are things like images, logo's etc that won't change.  They are held within your Django project tree and then copied to a static files folder held outside your project tree, normally within the web server root.  The copy is kicked off by the collectstatic command shown below.

1. Make sure that django.contrib.staticfiles is included in your INSTALLED_APPS.


2. Settings.py:

# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = 'C:/inetpub/wwwroot/static/'

# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'

# Additional locations of static files
STATICFILES_DIRS = (
  os.path.join(SITE_ROOT, 'static/'),
)
#STATICFILES_DIRS = (

3. Template:

    <img style= "width: 306px; height: 79px;" alt="my logo" src="{{ STATIC_URL }}logo.png" >

4.  Run python manage.py collectstatic to copy the static files into the STATICFILES_DIRS.

5.  In main urls.py:

    url(r'^static/(?P<path>.*)$', 'django.views.static.serve',
    {'document_root':settings.STATIC_ROOT}

6. For debug mode only add the following url to the urls.py file for the app (storing the static file folder):

if settings.DEBUG:
    urlpatterns += patterns( 'django.contrib.staticfiles.views' ,
        url(r '^static/(?P<path>.*)$', 'serve' ),
    )

Sunday, 25 August 2013

Django Model Forms Example

The Django documentation on Model Forms is thorough, but sometimes you want a simple example to explain the concept.   Here's one:

Model forms are used for the majority of forms in Django.  They're forms based on a model.  

Model on which the ModelForm is based:

class notification(models.Model):
    report_type = models.ForeignKey( 'report_type')
    event_type = models.ForeignKey( 'event_type')
    client_type = models.ForeignKey( 'client_type')
    email_addresses = models.TextField( 'Email Addresses (; separated)', null=True , max_length=500)

ModelForm:

from django.forms import ModelForm
from myapp.models import notification
from django.contrib.auth.models import User
from django.db.models import Q
from django.core.exceptions import ValidationError
import logging
log = logging.getLogger(__name__)

   
class notificationForm(ModelForm):   
    class Meta:
        model = Notification
    def clean_email_addresses(self):
        # check the email addresses are all for a registered user
        emails = self.cleaned_data['email_addresses']
        emaillist = emails.split( ";")
        if emaillist is not None:
            for e in emaillist:
                users = User.objects.filter(Q(email=e))
                if not users:
                    raise ValidationError( 'Each email address entered must be for a registered user of X-GenCA')
        return emails



View using the ModelForm:

# Create your views here.
from notification.forms import notificatioForm
from myapp.models import Notification
from django.shortcuts import redirect, render
from django.contrib.auth.decorators import login_required
import logging
log = logging.getLogger(__name__)
#command line debugging:
#import pdb; pdb.set_trace()

@login_required
def create_notification(request, template_name='notification/save.html' ):
    form = notificationForm(request.POST or None)
    if form.is_valid():
        form.save()
        return redirect( '/')
   
    return render(request, template_name, {'form':form,'notification' :Notification})


Template (save.html):

{% extends "base.html" %}
<h1 align="center">Create Notification</h1>
<form method="post" action= ".">
{% csrf_token %}
<div style="text-align: center;">
    {{ form.as_p }}
    <input type="submit" value= "Submit Form"/>
    </div>
</form>


URL pointing to View:

url(r'^create/$', views.create_notification), 
 
 
 
 The clean methods in the ModelForm can be used to add any business logic you require to validate the fields.  In the example above it checks the email addresses are those for registered users of the system.


Adding Authentication to a Django site


  1. Put 'django.contrib.auth' and 'django.contrib.contenttypes' in your INSTALLED_APPS setting. (The Permission model in django.contrib.auth depends on django.contrib.contenttypes.).  These should be the default settings anyway after installing Django.
  2. Run the command manage.py syncdb.
Add @login_required before each view:

e.g.
@login_required (login_url='/accounts/login/')
def myview(request, event_id):
    ...
    return render(request, ...

This redirects the user to /accounts/login, so add the following to the top level urls.py:

    url(r '^accounts/login/$' , 'django.contrib.auth.views.login'),


The default login page template is then at registration/login.html:

e.g. 
{% extends "base.html" %}
{% load url from future %}

{% if form.errors %}
<p>Your username and password didn 't match. Please try again.</p>
{% endif %}

<form method="post" action= "{% url 'django.contrib.auth.views.login' %}">
{% csrf_token %}
<table align="center">
<tr>
    <td>{{ form.username.label_tag }}</td>
    <td>{{ form.username }}</td>
</tr>
<tr>
    <td>{{ form.password.label_tag }}</td>
    <td>{{ form.password }}</td>
</tr>
</table>


<div style="text-align: center;"><input type="submit" value= "login" /></div>
<div style="text-align: center;"><input type="hidden" name= "next" value="{{ next }}" /></div>
</form>


The following URLs are used for password reset
(r'^accounts/password/reset/$', 'django.contrib.auth.views.password_reset', 
        {'post_reset_redirect' : '/accounts/password/reset/done/'}),(r'^accounts/password/reset/done/$', 'django.contrib.auth.views.password_reset_done'),(r'^accounts/password/reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', 'django.contrib.auth.views.password_reset_confirm', 
        {'post_reset_redirect' : '/accounts/password/done/'}),(r'^accounts/password/done/$', 'django.contrib.auth.views.password_reset_complete'),

The following templates are required. These are referred to from the url patterns above, plus one for  email.
  • registration/password_reset_complete.html
  • registration/password_reset_confirm.html
  • registration/password_reset_done.html
  • registration/password_reset_form.html
  • registration/password_reset_email.html

registration/password_reset_complete.html

{% extends "template.html" %}

{% block title %}Password reset complete{% endblock %}

{% block pagetitle %}Password reset complete{% endblock %}

{% block content %}<p>Your password has been set.  You may go ahead and log in now.</p><p><a href="{{ login_url }}">Log in>/a></p>{% endblock %}

registration/password_reset_confirm.html

{% extends "template.html" %}{% block title %}Password reset{% endblock %}

{% block pagetitle %}Password reset{% endblock %}

{% block content %}

{% if validlink %}<p>Please enter your new password twice so we can verify you typed it in correctly.</p><form action="" method="post">  <table>    <tr>      <td>{{ form.new_password1.errors }}<label for="id_new_password1">New password:</label></td>      <td>{{ form.new_password1 }}</td>    </tr>    <tr>      <td>{{ form.new_password2.errors }}<label for="id_new_password2">Confirm password:</label></td>      <td>{{ form.new_password2 }}</td>    </tr>    <tr>      <td></td>      <td><input type="submit" value="Change my password" /></td>    </tr>  </table></form>{% else %}<h1>Password reset unsuccessful</h1><p>The password reset link was invalid, possibly because it has already been used.  Please request a new password reset.</p>{% endif %}{% endblock %}

registration/password_reset_done.html

{% extends "template.html" %}

{% block title %}Password reset successful{% endblock %}

{% block pagetitle %}Password reset successful{% endblock %}

{% block content %}<p>We've e-mailed you instructions for setting your password to the e-mail address you submitted.</p><p>You should be receiving it shortly.</p>{% endblock %}

registration/password_reset_form.html

{% extends "template.html" %}

{% block title %}Password reset{% endblock %}

{% block pagetitle %}Password reset{% endblock %}

{% block content %}<p>Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one.</p>

<form action="" method="post"> {{ form.email.errors }}<p><label for="id_email">E-mail address:</label> {{ form.email }} <input type="submit" value="Reset my password" /></p></form>{% endblock %}

registration/password_reset_email.html

{% autoescape off %}You're receiving this e-mail because you requested a password reset for your user account at {{ site_name }}.

Please go to the following page and choose a new password:{% block reset_link %}{{ protocol }}://{{ domain }}{% url django.contrib.auth.views.password_reset_confirm uidb36=uid, token=token %}{% endblock %}

Your username, in case you've forgotten: {{ user.username }}

Thanks for using our site!

The {{ site_name }} team.

{% endautoescape %}

Using Django with SQLServer

Microsoft SQLServer shouldn't be your first choice as a database for a Django project as support for it is limited.  If however you HAVE to use SQLServer, then I've had success with SQLServer 2008 using django-pyodbc:

To install django-pyodbc, download the following and save to c:\django-pyodbc:  https://github.com/avidal/django-pyodbc/zipball/django-1.4  

Run python setup.py install (from the django-pyodbc folder).

Ensure sqlserver folder exists within c:\django-pyodbc.  Add c:\django-pyodbc to the PYTHONPATH  system variable.  The PYTHONPATH should be as follows:  C:\PYTHON27;C:\PYTHON27\DLLs;C:\PYTHON27\LIB;C:\PYTHON27\LIB\LIB-TK;c:\django-pyodbc

Then add the following to settings.py:

DATABASES = {
    'default': {
        'ENGINE': 'sql_server.pyodbc',
        'NAME': '<database_name>',                    
        'USER': '<username>',                     
        'PASSWORD': '<password>',                 
        'HOST':  r'<database server host name>',                      # Set to empty string for localhost.
          'DATABASE_ODBC_DSN': 'DSN name set up in Windows ODBC',
          'DATABASE_ODBC_DRIVER': 'SQL Server',
          'DATABASE_OPTIONS': {
            'driver': 'SQL Native Client',
               'MARS_Connection': True,
               }
     }
}

Replace <database_name> with the name of your database (you should create this using MySQL Workbench or from the MySQL command line first if it doesn't exist).

Replace <username> and <password> with the username and password required to access your MySQL database (use the MySQL Grant command to grant permissions to the user with the password).

Replace <database server host name> with the name of the machine hosting the MySQL database or leave this empty if the MySQL database is on your local machine. 


If you want to run Django on Linux connecting to SQLServer, then in my experience you may be in trouble.  django-pyodbc works to some extent on Linux, but I wouldn't recommend this combination.

Django Settings for MySQL

Building a Django site with a MySQL database back end is straightforward.  You'll need MySQL-Python ("pip install mysql-python" if you have python pip installed). 

Then configure the DATABASE settings in your projects settings.py:

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': '<database_name>',                    
            'USER': '<username>',     
            'PASSWORD': '<password>',
            'HOST':  r'<database server host name>',                      # Set to empty string for localhost.
        }
    }

Replace <database_name> with the name of your database (you should create this using MySQL Workbench or from the MySQL command line first if it doesn't exist).

Replace <username> and <password> with the username and password required to access your MySQL database (use the MySQL Grant command to grant permissions to the user with the password).

Replace <database server host name> with the name of the machine hosting the MySQL database or leave this empty if the MySQL database is on your local machine.

Using Eclipse for Django Development

I find Eclipse with PyDev a great Django development environment.

Download the standard version of Eclipse and then follow the Quick Install PyDev guide.

To set up PyDev, run Eclipse and set up the following:

From Eclipse Windows Preferences set up the Python Interpreter (this assumes you're using Python27):


Then I find it useful to set HTML files to be opened by the python editor:


From Project Properties (replace the green box with the name of your Django site):


 
Set up the Python path required for your Django site.  Here I've added my Django site and the path to a Django app /mysite/myapp:

Add any external libraries you are using.  Here I'm using django-pyodbc to use Django with a SQLServer 2008 database (I'll cover this in a separate post later):
 
 Set up DJANGO_MANAGE_LOCATION to the path to manage.py in your Django site, WSGIPythonPath to the wsgi.py file in your site and DJANGO_SETTINGS_MODULE to the settings.py file for your site: