Django_TailwindCSS_PostgreSQL Tailwind Templates

Django_tailwindcss_postgresql

Building a Django project (integrating TailwindCSS) using a PostgreSQL Database.

Django - TailwindCSS - PostgreSQL Polls APP

This repository aims at documenting the process of creating a Django project (integrated with TailwindCSS), which uses a PostgreSQL database. The polls app tutorial mentioned in the Django Documentation is used as a reference for making the notes. Links to useful resources are mentioned throughout this document.

TABLE OF CONTENTS

Initial Setup:

Create a Virtual Environment

user@device:~/projectDir$ python -m venv env

Activate Virtual Environment

user@device:~/projectDir$ env/Scripts/Activate

Install Django

user@device:~/projectDir$ pip install Django

Initialize Django Project

user@device:~/projectDir$ django-admin startproject projectname .

A look at the general file structure post initialization:

rootdir/
    projectname/
        __init.py__
        asgi.py
        wsgi.py
        settings.py
        urls.py
    db.sqlite3
    manage.py

Setup Database (PostgreSQL)

  • Install PostgreSQL (for local db hosting/testing) : Download Here

  • Open psql, you'll see the following prompt (note: the password for default user postgres, is the one you entered during the initial setup):

    PSQL(postgresql prompt), PS: you might need to add this script to environment variables first !

    Server [localhost]:
    Database [postgres]:
    Port [5432]:
    Username [postgres]:
    Password for user postgres:
    psql (14.1)
    WARNING: Console code page (437) differs from Windows code page (1252)
         8-bit characters might not work correctly. See psql reference
         page "Notes for Windows users" for details.
    Type "help" for help.
    
    postgres=#
    
  • Create custom user and allow db creation (optional):

    postgres=# CREATE USER username WITH PASSWORD 'password';
    postgres=# ALTER USER user CREATEDB;
    
  • Create db for your project:

    postgres=# CREATE DATABASE db_name WITH OWNER username ENCODING 'utf-8';
    

Integrate Database

  • Install psycopg2
    user@device:~/projectDir$ pip install psycopg2
    
  • Make changes to settings.py (present in the folder projectname):
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'NAME': 'dbname',
            'USER': 'dbowner_username',
            'PASSWORD': 'dbowner_password',
            'HOST': 'localhost',
            'PORT': '',
        }
    }
    
  • Also, change the timezone to cater to your region. For example, in India, the following setting would apply:
    TIME_ZONE = 'Asia/Kolkata'
    

That's it you're ready to start your project now!

  • Make migrations
    user@device:~/projectDir$ python ./manage.py makemigrations
    
  • Migrate changes (create default user tables etc.)
    user@device:~/projectDir$ python ./manage.py migrate
    
  • Create superuser for admin access:
    user@device:~/projectDir$ python ./manage.py createsuperuser
    

    Follow the prompts to enter required credentials, you may leave some fields blank by pressing enter!

  • Run server:
    user@device:~/projectDir$ python ./manage.py runserver
    

Notes

APPS

  • Creating an App(use case specific web application) within our project:

    user@device:~/projectDir$ python ./manage.py startapp appName
    
  • Following is the general structure of an App:

    appName/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        urls.py
        views.py
    
  • The app must then be added to INSTALLED_APPS in the settings.py file:

    INSTALLED_APPS = [
      'django.contrib.admin',
      'django.contrib.auth',
      'django.contrib.contenttypes',
      'django.contrib.sessions',
      'django.contrib.messages',
      'django.contrib.staticfiles',
    
      #app
      'appname'
    ]
    

VIEWS

A view is a “type” of web page in your Django application that generally serves a specific function and has a specific template.

  • Views related to an app (pages to be rendered) are defined in the views.py file. Each view, takes in a request parameter, and other optional params:

    def viewname(request, var1, var2, ...):
      return HttpResponse
    
  • Some views, may depend on existence of object/s. For these, we use the get_object_or_404/get_list_or_404 shortcuts:

    from .models import ModelName
    from django.shortcuts import get_object_or_404
    
    def viewname(request, id):
      obj = get_object_or_404(ModelName, pk=id)
      return HttpResponse
    
  • While updating object fields in views, a race condition might arise(two users parallelly updating the same fields leading to an error). To resolve this, we use the "F()" function, read the docs for the same here:

    https://docs.djangoproject.com/en/4.0/ref/models/expressions/#f-expressions

    TEMPLATES

    • Templates (Django-Html docs) are defined inside a templates folder local to the app directory. So as to not conflict with similar template names in other "app-template" directories, we create a subfolder inside the template directory with the name of the app. The app folder structure should look similar to this:

      appName/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
        models.py
        tests.py
        templates/
            appName/
                templatename.html
        urls.py
        views.py
      
    • In order to render HTML templates instead of plain Http Responses, we use the following django shortcut:

      Here, context defines the variables to be passed to the HTML template.

      from django.shortcuts import render
      
      def viewname(request, var1, var2, ...):
        context = {
          "var1" : var1,
          "var2" : var2
        }
        return render(request, "template path inside local templates folder", context)
      
    • A sample template (here, the variable latest_question_list, was passed via context):

      {% if latest_question_list %}
        <ul>
          {% for question in latest_question_list %}
              <li><a href="{% url 'polls:detail' question.id %}">{{ forloop.counter }}. {{ question.question_text }}</a></li>
          {% endfor %}
        </ul>
      {% else %}
        <p>No polls are available.</p>
      {% endif %}
      

    GENERIC/CLASS BASED

FORMS

  • Django provides a safe system for protection against Cross Site Reqest Forgeries. Thus, for each form defined in out templates, we use the {% csrf_token %} template tag.
    <form action="{% url 'view_name' args %}" method="post/get">
    {% csrf_token %}
      <!-- form fields -->
    </form>
    
  • The name attribute of each form field, defines it's access name in the post data.
    request.POST['field_name']
    

URLS

  • URL wrt to each view must be added to the urls file local to the app. A url may contain path string and variables to be passed along to its corresponding view:

    from django.urls import path
    
    from . import views
    
    app_name = "appname" # defines namespace for app urls, avoids url conflicts
    urlpatterns = [
        path('<param_type:param_name>/pathstring/', views.viewname, name='view_name'),
    ]
    
  • These urls local to the app must be then pointed to by the root URLConf residing in the projectname folder.

    from django.contrib import admin
    from django.urls import include, path
    
    urlpatterns = [
        path('appname/', include('appname.urls')),
        path('admin/', admin.site.urls),
    ]
    
  • URLs may be accessed in the templates, as follows:

    <a href="{% url 'app_name:url_name' args %}"> {{ var_link_name }} </a>
    
  • A URL, can be generated from the name of the view it's connected to, by using the reverse function:

    from django.http import HttpResponseRedirect
    from django.urls import reverse
    
    def viewname(request, args):
      # ...
      return HttpResponseRedirect(reverse('appname:viewname', args=(arg1, arg2, ...)))
    

MODELS

  • Models related to an app are defined in the models.py file local to the app.

    from django.db import models
    
    class ModelName(models.Model):
      model_field = models.FieldName(params)
    
  • A foreign key may be established from one model instance to the other, as follows:

    class ModelOne(models.Model):
      # ...
      model_field = models.FieldName(params)
    
    class ModelTwo(models.Model):
      modelOne = models.ForeignKey(ModelOne, on_delete=models.CASCADE)
      # ...
      model_field = models.FieldName(params)
    
  • Each model has an object representation (id by default), which can be changed by defining the __str__ function:

    class ModelName(models.Model):
      # ...
      def __str__(self):
          return self.paramToRender
    
  • Similarly, custom methods can be added to the Model, taking self as the input parameter to refer the model's fields.

    class ModelName(models.Model):
      # ...
      def customMethod(self):
        # ...
        return result
    
  • Once models have been defined, migrations must be made in order to update the database:

    user@device:~/projectDir$ python ./manage.py makemigrations appName
    user@device:~/projectDir$ python ./manage.py migrate
    

QUERYING & WORKING WITH MODELS

  • Post migrations, the model can then be interacted with via a shell:

    user@device:~/projectDir$ python ./manage.py shell
    >>>
    
  • Importing models into the shell

    >>> from appname.models import ModelName
    
  • Displaying all objects in a model

    >>> ModelName.objects.all()
    
  • Creating model objects

    >>> obj = ModelName(param1=val1, param2=val2...)
    >>> obj.save()
    
  • Getting objects params:

    >>> obj.param
    
  • Changing object param values:

    >>> obj.param = newValue
    >>> obj.save()
    
  • To get a particular object on basis of field value (returns Object):

    >>> ModelName.objects.get(field=val)
    
  • Filtering objects on basis of field value (return QuerySet):

    >>> ModelName.objects.filter(field=val)
    
  • Double underscores are used to seperate relationships, for example, consider the following object question:

    question:
      pub_date:
        year
        month
        day
      question_text:
    

    The year attribute, for example can be represented as follows: questionpub_dateyear

  • Consider the following Question, Choice models:

    class Question(models.Model):
      question_text = models.CharField(max_length=200)
      pub_date = models.DateTimeField('date published')
    
    
    class Choice(models.Model):
        question = models.ForeignKey(Question, on_delete=models.CASCADE)
        choice_text = models.CharField(max_length=200)
        votes = models.IntegerField(default=0)
    
    • Here, we can access the choice objects linked to a particular Question object (say q), as follows:
      q.choice_set.all()
      # and for the count:
      q.choice_set.count()
      # similarly filtering can be done too:
      q.choice_set.filter(choice_text__startswith='Choice Value')
      
    • Similarly details of question related to a choice can be accessed, as if it were the object's own parameters:
      Choice.objects.filter(question__pub_date__year=current_year)
      

DJANGO ADMIN

Customizing admin page (& custom templates) : https://docs.djangoproject.com/en/4.0/intro/tutorial07/

  • In order to access created Models on the admin dashboard interface (http://127.0.0.1:8000/admin/), we must register our models on the admin site. To do this, we make the following updates in the admin.py file local to the App containing the Models we wish to register:

    from django.contrib import admin
    
    from .models import ModelName
    
    admin.site.register(ModelName)
    

TESTING

Test are routines that check the operation of your code. They might work upon a tiny detail (unit tests) or examine the overall operation of the software (integration tests).

  • Some best practices while defining tests are :

    • a separate TestClass for each model or view
    • a separate test method for each set of conditions you want to test
    • test method names that describe their function
  • We conventionaly define the test cases for an app in its tests.py file. We create a class catering to the model/view we wish to test and inherit from the django.test.TestCase Class.

    from django.test import TestCase
    
    class NameModelTests(TestCase):
      # ...
      self.assertIs(val1, val2)
      # or/and
      self.assertContains(val3, val4)
      # or/and
      response = self.client.get(url)
      self.assertQuerysetEqual(response.context['attribute'], [...])
    
  • Read more on the same, here.

STATIC FILES

  • Static files (imgs/js/css) are defined inside a static folder local to the app directory. So as to not conflict with similar static files names in other "app-static" directories, we create a subfolder inside the static directory with the name of the app. The app folder structure should look similar to this:
    appName/
      __init__.py
      admin.py
      apps.py
      migrations/
          __init__.py
      models.py
      tests.py
      templates/
          appName/
      static/
          appName/
            staticfile
            ...
      urls.py
      views.py
    
  • To use the static files in a template, we then use {% load static %}, to enable usage of the {% static %} template tag, which generates the absolute URL of static file.
    {% load static %}
    <link rel="stylesheet" type="text/css" href="{% static 'appname/style.css' %}">
    

Styling Via TailwindCSS

Use the django-tailwind library to integrate tailwind development with django, read the docs here.

  • Install the django-tailwind library:

    user@device:~/projectDir$ pip install django-tailwind
    
  • Add tailwind to the installed apps (in settings.py file):

    INSTALLED_APPS = [
        # ...
        # TailwindCSS
        'tailwind',
    ]
    
  • Create a theme app, which will provide tailwind styles to the whole project:

    user@device:~/projectDir$ python manage.py tailwind init theme
    
  • Add the theme app to the installed apps and configure the TAILWIND_APP_NAME variable.

    INSTALLED_APPS = [
        # ...
        # TailwindCSS
        'tailwind',
        'theme',
    ]
    
    TAILWIND_APP_NAME = 'theme'
    
  • Install NodeJs

  • Get NodeJs system path:

    • Linux:
      user@device:~/projectDir$ which npm
      /usr/local/bin/npm
      
    • Windows:
      user@device::~/projectDir$ gcm npm
      CommandType     Name        Version    Source
      -----------     ----        -------    ------
      Application     npm.cmd     0.0.0.0    C:\Program Files\nodejs\npm.cmd
      
  • Add path to settings.py file

    • Linux
      # rest of the settings
      NPM_BIN_PATH = r"/usr/local/bin/npm" # add this line
      
    • Windows
      # rest of the config
      NPM_BIN_PATH = r"C:\Program Files\nodejs\npm.cmd" # add this line
      
  • Install TailwindCSS dependencies:

    user@device:~/projectDir$ python manage.py tailwind install
    
  • For using tailwind in any template of any registered app, do the following:

    {% load static tailwind_tags %}
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <!-- rest of the header -->
        {% tailwind_preload_css %}
        {% tailwind_css %}
      </head>
      <body>
        <!-- body content -->
      </body>
    </html>
    
  • For rendering, compile "used" tailwind tags by running tailwind (only required for development) prior runserver:

    <!-- console 1 -->
    user@device:~/projectDir$ python manage.py tailwind start
    <!-- console 2 -->
    user@device:~/projectDir$ python manage.py runserver
    
  • To create a production build of your theme, run:

    user@device:~/projectDir$ python manage.py tailwind build
    

Top categories

Loading Svelte Themes