OverIQ.com

Revisiting cadmin App

Last updated on July 27, 2020


In this lesson, we will be building remaining pages of our cadmin app. Currently, our cadmin app has following pages:

  • Login page (http://127.0.0.1:8000/cadmin/login/)
  • Logout page (http://127.0.0.1:8000/cadmin/logout/)
  • Password change page (http://127.0.0.1:8000/cadmin/password-change/)
  • Password change done page (http://127.0.0.1:8000/cadmin/password-change-done/)
  • User registration page (http://127.0.0.1:8000/cadmin/register/)
  • Activate account page (http://127.0.0.1:8000/cadmin/activate/account/)
  • Post add/update page (http://127.0.0.1:8000/cadmin/post/add/ and http://127.0.0.1:8000/cadmin/post/update/<post_id>/)

In the upcoming section, we are creating the following pages:

  • Post list page - to display a paginated list of posts.
  • Tag list page - to display a paginated list of tags.
  • Tag add page.
  • Tag update page.
  • Tag delete page.
  • Category list page - to display a paginated list of categories.
  • Category add page.
  • Category update page.
  • Category delete page.
  • Account Info page - to display the details of logged in user.

In addition to that, we will also modify Post add and update page.

Let's start by modifying Post add page.

Modifying Post Add Page #

As the situation stands everytime we add or update a post we are presented with an author dropdown list, which is unnecessary. Instead of showing an author dropdown list, it would be much better if we automatically associate the post with the logged in author and only show author dropdown list to site superuser's ( i.e users whose is_superuser attribute is True ).

Further, It will not be mandatory for the superuser to select the author while adding a post. Because we will automatically assign a special account named staff to the post in case the author is not selected. Okay, let's implement this:

Open post_add.html and modify the file as follows:

TGDB/django_project/cadmin/templates/cadmin/post_add.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
{% extends "cadmin/base_admin.html" %}

{% block title %}
    Add New Post - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="main">

        <p>&#187; <a href="{% url 'post_list' %}">All Posts</a> &#187; Add Post</p>

        <h3>Add Post</h3>

        {% if messages %}
        <ul class="messages">
            {% for message in messages %}
            <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
            {% endfor %}
        </ul>
        {% endif %}

        <p>
            {{ form.non_field_errors }}
        </p>

        <form action="" method="post">
            {% csrf_token %}
            <table>
                <tr>
                    <td>{{ form.title.label_tag }}</td>
                    <td>
                        {{ form.title.errors }}
                        {{ form.title }}
                    </td>
                </tr>
                <tr>
                    <td>{{ form.content.label_tag }}</td>
                    <td>
                        {{ form.content.errors }}
                        {{ form.content }}
                    </td>
                </tr>
                <tr>
                    <td>{{ form.category.label_tag }}</td>
                    <td>
                        {{ form.category.errors }}
                        {{ form.category }}
                    </td>
                </tr>
                <tr>
                    <td>{{ form.tags.label_tag }}</td>
                    <td>
                        {{ form.tags.errors }}
                        {{ form.tags }}
                    </td>
                </tr>

                {#  Show authors only if logged in user is a superuser  #}
                {% if request.user.is_superuser %}
                    <tr>
                        <td>{{ form.author.label_tag }}</td>
                        <td>
                            {{ form.author.errors }}
                            {{ form.author }}
                        </td>
                    </tr>
                {% endif %}
                <tr>
                    <td></td>
                    <td><input type="submit" value="Add Post"></td>
                </tr>
            </table>
        </form>

    </div>

{% endblock %}

Here we are rendering each form field manually. Notice that we are only displaying author list when the logged in author is superuser. So, essentially we want to make author field optional but the problem is our current state of the Post model doesn't allow that. To view this problem login to cadmin app using a non-superuser account and try creating a new post.

After hitting the save button you will be redirected to Post add page again. So what's going on? The problem is that the form validation failed because we have not provided any value to the author field.

But! Where are the errors?

We didn't get any error messages because we have hidden the author dropdown field. To view the error message, comment out the if tag (line 58) in post_add.html as follows:

TGDB/django_project/cadmin/templates/cadmin/post_add.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{# ... #}
{#  Show authors only if logged in user is a superuser  #}
{# {% if request.user.is_superuser %} #}
    <tr>
        <td>{{ form.author.label_tag }}</td>
        <td>
            {{ form.author.errors }}
            {{ form.author }}
        </td>
    </tr>
{# {% endif %} #}
{# ... #}

Hit the save button again and you will be displayed a form like this:

One way to solve this problem is to make author field optional in PostForm class. To do so, modify PostForm class in blog's forms.py as follows:

TGDB/django_project/blog/forms.py

1
2
3
4
5
6
7
8
#...
class PostForm(forms.ModelForm):    
    author = forms.ModelChoiceField(queryset=Author.objects.all(), required=False)

    class Meta:
        model = Post
        fields = ('title', 'content', 'author', 'category', 'tags',)
        #...

Here we are redefining author field as ModelChoiceField field. The ModelChoiceField will be rendered as dropdown list and will use data specified in the queryset parameter to populate the field. The required=False makes the author field optional.

Next, we need a new special Author account to assign post in case a superuser not selected any author from the dropdown list.

Create a new Author with username staff by visiting http://127.0.0.1:8000/cadmin/register/ page, then verify the account the by clicking email verification link sent to the email. Hop back to the Django shell and set the is_superuser attribute of the user we just created to True.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
>>>
>>> from blog.models import Author
>>>
>>> u = Author.objects.get(user__username='staff')
>>> u
<Author: staff>
>>>
>>> u.user
<User: staff>
>>>
>>> u.user.is_superuser
False
>>>
>>> u.user.is_superuser = True
>>>
>>> u.user.save()
>>>

Instead of using Django Shell, we could have also used Django Admin for this change.

Now the only thing remain is to update our post_add() view. Open post_add() view and modify it as follows:

TGDB/django_project/cadmin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#...
@login_required
def post_add(request):
    # If request is POST, create a bound form(form with data)
    if request.method == "POST":
        f = PostForm(request.POST)

        # check whether form is valid or not
        # if the form is valid, save the data to the database
        # and redirect the user back to the add post form

        # If form is invalid show form with errors again
        if f.is_valid():
            # if author is not selected and user is superuser, then assign the post to the author named staff
            if request.POST.get('author') == "" and request.user.is_superuser:
                new_post = f.save(commit=False)
                author = Author.objects.get(user__username='staff')
                new_post.author = author
                new_post.save()
                f.save_m2m()

            # if author is selected and user is superuser
            elif request.POST.get('author') and request.user.is_superuser:
                new_post = f.save()

            # if user is not a superuser
            else:
                new_post = f.save(commit=False)
                new_post.author = Author.objects.get(user__username=request.user.username)
                new_post.save()
                f.save_m2m()

            messages.add_message(request, messages.INFO, 'Post added')
            return redirect('post_add')

    # if request is GET the show unbound form to the user
    else:
        f = PostForm()
    return render(request, 'cadmin/post_add.html', {'form': f})
#...

There is nothing new in the above code, except commit=False and save_m2m() method. As you already know, calling save() method on a ModelForm class saves the object into the database. When we pass commit=False to the save() method, then the save() method just returns the object instead of saving it into the database. We do this usually when we want to add some additional data to the object. In this case, we are assigning author object to the post.

A side effect of passing commit=True to save() method is that, if your model has many-to-many relation with other models, then Django will not save the data for many-to-many relation. To solve this issue Django provides a method called save_m2m() to every ModelForm class. Calling save_m2m() saves the data for many-to-many relation.

To view the fruits of our labor login to cadmin app using a superuser account (like the staff) and create a new post by visiting http://127.0.0.1:8000/cadmin/post/add/ page.

Enter the data in all the fields except author and hit "Add Post" to create a new post.

This action will cause the execution of following snippet in the post_add() view:

TGDB/django_project/cadmin/views.py

1
2
3
4
5
6
7
8
9
#...
# if author is not selected and user is superuser, then assign the post to the author named staff
if request.POST.get('author') == "" and request.user.is_superuser:
    new_post = f.save(commit=False)
    author = Author.objects.get(user__username='staff')
    new_post.author = author
    new_post.save()
    f.save_m2m()
#...

Create another post but this time select an author from the dropdown list.

This action will trigger the execution of elif block in post_add() view:

TGDB/django_project/cadmin/views.py

1
2
3
4
5
#...
# if author is selected and user is superuser
elif request.POST.get('author') and request.user.is_superuser:
    new_post = f.save()
#...

Finally, logout from cadmin app and login again using a non-superuser account. And create a new post again by visiting http://127.0.0.1:8000/cadmin/post/add/ page.

As you can see the logged in user is not allowed to select author. Enter some data in the fields and hit "Add Post" button. This action will execute else block in the post_add() view:

TGDB/django_project/cadmin/views.py

1
2
3
4
5
6
7
8
#...
# if user is not a superuser
 else:
    new_post = f.save(commit=False)
    new_post.author = Author.objects.get(user__username=request.user.username)
    new_post.save()
    f.save_m2m()
#...

Modifying Post Update Page #

Open cadmin's views.py and amend post_update() view as follows:

TGDB/django_project/cadmin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#...
@login_required
def post_update(request, pk):
    post = get_object_or_404(Post, pk=pk)

    # If request is POST, create a bound form(form with data)
    if request.method == "POST":
        f = PostForm(request.POST, instance=post)

        # check whether form is valid or not
        # if the form is valid, save the data to the database
        # and redirect the user back to the update post form

        # If form is invalid show form with errors again
        if f.is_valid():
            # if author is not selected and user is superuser, then assign the post to the author named staff
            if request.POST.get('author') == "" and request.user.is_superuser:
                updated_post = f.save(commit=False)
                author = Author.objects.get(user__username='staff')
                updated_post.author = author
                updated_post.save()
                f.save_m2m()
            # if author is selected and user is superuser
            elif request.POST.get('author') and request.user.is_superuser:
                updated_post = f.save()
            # if user is not a superuser
            else:
                updated_post = f.save(commit=False)
                updated_post.author = Author.objects.get(user__username=request.user.username)
                updated_post.save()
                f.save_m2m()

            messages.add_message(request, messages.INFO, 'Post updated')
            return redirect(reverse('post_update', args=[post.id]))

    # if request is GET the show unbound form to the user
    else:
        f = PostForm(instance=post)

    return render(request, 'cadmin/post_update.html', {'form': f, 'post': post})
#...

Next, open post_update.html and modify the page as follows:

TGDB/django_project/cadmin/templates/cadmin/post_update.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
{% extends "cadmin/base_admin.html" %}

{% block title %}
    Update Post - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="main">
        <p>&#187; <a href="{% url 'post_list' %}">All Posts</a> &#187; Post Update</p>

        <h3>Post Update</h3>

        {% if messages %}
        <ul class="messages">
            {% for message in messages %}
            <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
            {% endfor %}
        </ul>
        {% endif %}

        <p>
            {{ form.non_field_errors }}
        </p>

        <form action="" method="post">
            {% csrf_token %}
            <table>
                <tr>
                    <td>{{ form.title.label_tag }}</td>
                    <td>
                        {{ form.title.errors }}
                        {{ form.title }}
                    </td>
                </tr>
                <tr>
                    <td>{{ form.content.label_tag }}</td>
                    <td>
                        {{ form.content.errors }}
                        {{ form.content }}
                    </td>
                </tr>
                <tr>
                    <td>{{ form.category.label_tag }}</td>
                    <td>
                        {{ form.category.errors }}
                        {{ form.category }}
                    </td>
                </tr>
                <tr>
                    <td>{{ form.tags.label_tag }}</td>
                    <td>
                        {{ form.tags.errors }}
                        {{ form.tags }}
                    </td>
                </tr>
                {#  Show authors only if logged in user is a superuser  #}
                {% if request.user.is_superuser %}
                    <tr>
                        <td>{{ form.author.label_tag }}</td>
                        <td>
                            {{ form.author.errors }}
                            {{ form.author }}
                        </td>
                    </tr>
                {% endif %}
                <tr>
                    <td></td>
                    <td><input type="submit" value="Update Post"></td>
                </tr>
            </table>
        </form>

    </div>

{% endblock %}

Now just like with Post Add page Author dropdown list will only appear in when the logged in user is a superuser.

Creating Post List Page #

Open cadmin's views.py and add post_list() view as follows:

TGDB/django_project/cadmin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#...
def activate_account(request):
    #...

@login_required
def post_list(request):
    if request.user.is_superuser:
        posts = Post.objects.order_by("-id").all()
    else:
        posts = Post.objects.filter(author__user__username=request.user.username).order_by("-id")
    
    posts = helpers.pg_records(request, posts, 5)

    return render(request, 'cadmin/post_list.html', {'posts': posts})
#...

The post_list() view displays a list of post in reverse chronological order (i.e latest first, oldest last). If logged in user is a superuser then it will display all the posts in the database. On the other hand, if logged in user is non-superuser it will only display posts created by himself.

Next, create post_list.html template with the following code:

TGDB/django_project/cadmin/templates/cadmin/post_list.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
{% extends "cadmin/base_admin.html" %}

{% block title %}
    All Posts - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="main">

        <p>&#187; <a href="{% url 'post_list' %}">All Post</a> </p>

        <p class="button"><a href="{% url 'post_add' %}">Add Post &raquo;</a></p>

        {% if messages %}
            {% for message in messages %}
            <p>{{ message }}</p>
            {% endfor %}
        {% endif %}

        <p class="count">Total Posts: {{ posts.paginator.count }}</p>
        
            {% for post in posts %}
                {% if forloop.first     %}
                    <table class="tbl-class">
                        <tr>
                            <th>Title</th>
                            <th>Category</th>
                            {% if request.user.is_superuser %}
                                <th>Author</th>
                            {% endif %}
                            <th>Date</th>
                            <th>Action</th>
                        </tr>
                {% endif %}

                <tr>
                    <td class="post-title" title="{{ post.title }}">{{ post.title|truncatechars:50 }}</td>
                    <td>{{ post.category }}</td>
                    {% if request.user.is_superuser %}
                        <td>{{ post.author }}</td>
                    {% endif %}

                    <td title="{{ post.pub_date }}">{{ post.pub_date|date:"d M Y" }}</td>
                    <td>
                        <a href="{% url 'post_update' post.id %}">Edit</a> |
                        <a href="{% url 'post_delete' post.id %}?next={{ request.get_full_path }}" onclick=" return confirm('Are you sure ?')">Delete</a>
                    </td>
                </tr>

                {% if forloop.last %}
                    </table>
                {% endif %}

            {% empty %}
                No Posts
            {% endfor %}

    </div>

    {% if posts %}

        <div class="pagination">
            <p>
                {% if posts.has_previous %}
                    <a href="?page={{ posts.previous_page_number }}">&lt; Prev</a> |
                {% endif %}

                {% if posts.has_next %}
                    <a href="?page={{ posts.next_page_number }}">Next &gt;</a>
                {% endif %}

                <span>Page {{ posts.number }} of {{ posts.paginator.num_pages }}</span>
            </p>
        </div>

    {% endif %}

{% endblock %}

We want users to show a list of post at the root URL of cadmin app i.e http://127.0.0.1:8000/cadmin/. Currently, the root URL of cadmin app points to home() view function.

TGDB/django_project/cadmin/urls.py

1
2
3
4
5
6
#...
urlpatterns = [
    #...
    url(r'^$', views.home, name='home'),
    #...
]

Update the above URL pattern as follows:

TGDB/django_project/cadmin/urls.py

1
2
3
4
5
6
#...
urlpatterns = [
    #...
    url(r'^$', views.post_list, name='post_list'),
    #...
]

Right now if you visit post list page (http://127.0.0.1:8000/cadmin/), you will get an error as follows:

This is because we haven't yet created any URL pattern to delete post objects.

Deleting Posts #

To allow deletion of post from cadmin add post_delete() view at the end of the file as follows:

TGDB/django_project/cadmin/views.py

1
2
3
4
5
6
7
8
9
#...

@login_required
def post_delete(request, pk):
    post = get_object_or_404(Post, pk=pk)
    post.delete()
    next_page = request.GET['next']
    messages.add_message(request, messages.INFO, 'Post deleted')
    return redirect(next_page)

Now open cadmin's urls.py and add the following URL pattern to just below post_update URL pattern.

TGDB/django_project/cadmin/urls.py

1
2
3
4
5
6
#...
urlpatterns = [
    #...
    url(r'^post/update/(?P<pk>[\d]+)/$', views.post_update, name='post_update'),
    url(r'^post/delete/(?P<pk>[\d]+)/$', views.post_delete, name='post_delete'),
]

To view our changes in action visit http://127.0.0.1:8000/cadmin/ and you should see a link to delete post beside Edit link as follows:

Creating Category List Page #

Open views.py and add category_list() view just below post_delete() view as follows:

TGDB/django_project/cadmin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#...
def post_delete(request, pk):
   #...

@login_required
def category_list(request):
    if request.user.is_superuser:
        categories = Category.objects.order_by("-id").all()
    else:
        categories = Category.objects.filter(author__user__username=request.user.username).order_by("-id")

    categories = helpers.pg_records(request, categories, 5)

    return render(request, 'cadmin/category_list.html', {'categories': categories})

Create a new template called category_list.html with the following code:

TGDB/django_project/cadmin/templates/cadmin/category_list.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
{% extends "cadmin/base_admin.html" %}

{% block title %}
    All Posts - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="main">

        <p>&#187; <a href="{% url 'category_list' %}">All Categories</a></p>

        <p class="button"><a href="{% url 'category_add' %}">Add Category &raquo;</a></p>

        {% if messages %}
            {% for message in messages %}
            <p>{{ message }}</p>
            {% endfor %}
        {% endif %}

        <p class="count">Total Categories: {{ categories.paginator.count }}</p>

            {% for category in categories %}
                {% if forloop.first     %}
                    <table class="tbl-class">
                        <tr>
                            <th>Name</th>
                            <th>Slug</th>
                            <th>Author</th>
                            <th>Action</th>
                        </tr>
                {% endif %}

                <tr>
                    <td class="post-title" title="{{ category.name }}">{{ category.name|truncatechars:50 }}</td>
                    <td>{{ category.slug }}</td>
                    <td>{{ category.author.user.username }}</td>
                    <td>
                        <a href="{% url 'category_update' category.id %}">Edit</a> |
                        <a href="{% url 'category_delete' category.id %}?next={{ request.get_full_path }}" onclick=" return confirm('Are you sure ?')">Delete</a>
                    </td>
                </tr>

                {% if forloop.last %}
                    </table>
                {% endif %}

            {% empty %}
                No Posts
            {% endfor %}

        </table>

    </div>

    {% if categories %}

        <div class="pagination">
            <p>
                {% if categories.has_previous %}
                    <a href="?page={{ categories.previous_page_number }}">&lt; Prev</a> |
                {% endif %}

                {% if categories.has_next %}
                    <a href="?page={{ categories.next_page_number }}">Next &gt;</a>
                {% endif %}

                <span>Page {{ categories.number }} of {{ categories.paginator.num_pages }}</span>
            </p>
        </div>

    {% endif %}

{% endblock %}

Next, add the following URL pattern just below post_delete URL pattern as follows:

TGDB/django_project/cadmin/urls.py

1
2
3
4
5
6
#...
urlpatterns = [
    #...
    url(r'^post/delete/(?P<pk>[\d]+)/$', views.post_delete, name='post_delete'),
    url(r'^category/$', views.category_list, name='category_list'),
]

Creating Category Add/Update Page #

Category Add/Update page will be similar to Post Add/Update page. Here too we will display author dropdown list only to the superusers.

First open blog's forms.py and redefine the author field in CategoryForm class as follows:

TGDB/django_project/blog/forms.py

1
2
3
4
5
6
7
#...
class CategoryForm(forms.ModelForm):
    author = forms.ModelChoiceField(queryset=Author.objects.all(), required=False)

    class Meta:
        model = Category    
    #...

Open cadmin's views.py and add category_add() and category_update() view as follows:

TGDB/django_project/cadmin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#...
from django.shortcuts import render, redirect, get_object_or_404, reverse, Http404
from blog.forms import PostForm, CategoryForm
#...
from django.conf import settings
#...

# view to add new category
@login_required
def category_add(request):

    # If request is POST, create a bound form(form with data)
    if request.method == "POST":
        f = CategoryForm(request.POST)

        # check whether form is valid or not
        # if the form is valid, save the data to the database
        # and redirect the user back to the add post form

        # If form is invalid show form with errors again
        if f.is_valid():
            # new_category = f.save()
            # new_category = f.save(commit=False)
            # new_category.author = get_user(request)
            # new_category.save()

            if request.POST.get('author') == "" and request.user.is_superuser:
                # if author is not supplied and user is superuser
                new_category = f.save(commit=False)
                author = Author.objects.get(user__username='staff')
                new_category.author = author
                new_category.save()
            elif request.POST.get('author') and request.user.is_superuser:
                # if author is supplied and user is superuser
                new_category = f.save()
            else:
                # if author not a superuser
                new_category = f.save(commit=False)
                new_category.author = Author.objects.get(user__username=request.user.username)
                new_category.save()

            messages.add_message(request, messages.INFO, 'Category added')
            return redirect('category_add')

    # if request is GET the show unbound form to the user
    else:
        f = CategoryForm()

    return render(request, 'cadmin/category_add.html', {'form': f})


# view to update category
def category_update(request, pk):
    category = get_object_or_404(Category, pk=pk)

    # If request is POST, create a bound form(form with data)
    if request.method == "POST": # If request is POST, create a bound form
        f = CategoryForm(request.POST, instance=category)

        # check whether form is valid or not
        # if the form is valid, save the data to the database
        # and redirect the user back to the category form

        # If form is invalid show form with errors again
        if f.is_valid():

            if request.POST.get('author') == "" and request.user.is_superuser:
                # if author is not supplied and user is superuser
                updated_category = f.save(commit=False)
                author = Author.objects.get(user__username='staff')
                updated_category.author = author
                updated_category.save()
            elif request.POST.get('author') and request.user.is_superuser:
                # if author is supplied and user is superuser
                updated_category = f.save()
            else:
                # if author not a superuser
                updated_category = f.save(commit=False)
                updated_category.author = Author.objects.get(user__username=request.user.username)
                updated_category.save()

            new_category = f.save()
            messages.add_message(request, messages.INFO, 'Category updated')
            return redirect(reverse('category_update', args=[category.id]))

    # if request is GET the show unbound form to the user
    else:
        f = CategoryForm(instance=category)

    return render(request, 'cadmin/category_update.html', {'form': f, 'category': category})

Create two new templates category_add.html and category_update.html with the following code:

TGDB/django_project/cadmin/templates/cadmin/category_add.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{% extends "cadmin/base_admin.html" %}

{% block title %}
    Add New Category - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="main">

        <p>&#187; <a href="{% url 'category_list' %}">All Category</a> &#187; Add Category</p>

        <h3>Add Category</h3>

        {% if messages %}
            {% for message in messages %}
            <p>{{ message }}</p>
            {% endfor %}
        {% endif %}

        {{ form.non_field_errors }}

        <form action="" method="post">
                {% csrf_token %}
            <table>
                <tr>
                    <td>{{ form.name.label_tag }}</td>
                    <td>
                        {{ form.name.errors }}
                        {{ form.name }}
                    </td>
                </tr>
                <tr>
                    <td>{{ form.slug.label_tag }}</td>
                    <td>
                        {{ form.slug.errors }}
                        {{ form.slug }}
                    </td>
                </tr>

                {% if request.user.is_superuser %}
                    <tr>
                        <td>{{ form.author.label_tag }}</td>
                        <td>
                            {{ form.author.errors }}
                            {{ form.author }}
                        </td>
                    </tr>
                {% endif %}

                <tr>
                    <td></td>
                    <td><input type="submit" value="Add Category"></td>
                </tr>

            </table>
        </form>

    </div>

{% endblock %}

TGDB/django_project/cadmin/templates/cadmin/category_update.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
{% extends "cadmin/base_admin.html" %}

{% block title %}
    Add Category - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="main">

        <p>&#187; <a href="{% url 'category_list' %}">All Category</a> &#187; Update Category</p>

        <h3>Update Category</h3>

        {% if messages %}
            {% for message in messages %}
            <p>{{ message }}</p>
            {% endfor %}
        {% endif %}

        {{ form.non_field_errors }}

        <form action="" method="post">
                {% csrf_token %}
            <table>
                <tr>
                    <td>{{ form.name.label_tag }}</td>
                    <td>
                        {{ form.name.errors }}
                        {{ form.name }}
                    </td>
                </tr>
                <tr>
                    <td>{{ form.slug.label_tag }}</td>
                    <td>
                        {{ form.slug.errors }}
                        {{ form.slug }}
                    </td>
                </tr>

                {% if request.user.is_superuser %}
                    <tr>
                        <td>{{ form.author.label_tag }}</td>
                        <td>
                            {{ form.author.errors }}
                            {{ form.author }}
                        </td>
                    </tr>
                {% endif %}

                <tr>
                    <td></td>
                    <td><input type="submit" value="Update Category"></td>
                </tr>

            </table>
        </form>

    </div>

{% endblock %}

At last, add the following URL patterns to the end of urlpatterns list in cadmin's urls.py as follows:

TGDB/django_project/cadmin/urls.py

1
2
3
4
5
6
7
8
#...
urlpatterns = [
    #...
    url(r'^category/$', views.category_list, name='category_list'),
    url(r'^category/add/$', views.category_add, name='category_add'),
    url(r'^category/update/(?P<pk>[\d]+)/$', views.category_update, name='category_update'),
    #...
]

Deleting Categories #

In cadmin's views.py, add the category_delete() view just after category_update() as follows:

TGDB/django_project/cadmin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#...
def category_update(request, pk):
    #...

@login_required
def category_delete(request, pk):
    category = get_object_or_404(Category, pk=pk)
    category.delete()
    next_page = request.GET['next']
    messages.add_message(request, messages.INFO, 'Category deleted')
    return redirect(next_page)

Next, add the following URL pattern at the end of urlpatterns list:

TGDB/django_project/cadmin/urls.py

1
2
3
4
5
6
#...
urlpatterns = [
    #...
url(r'^category/update/(?P<pk>[\d]+)/$', views.category_update, name='category_update'),
    url(r'^category/delete/(?P<pk>[\d]+)/$', views.category_delete, name='category_delete'),
]

Creating Tag Pages #

First open blog's forms.py and redefine the author field in TagForm class as follows:

TGDB/django_project/blog/forms.py

1
2
3
4
5
6
7
8
#...
class TagForm(forms.ModelForm):
    author = forms.ModelChoiceField(queryset=Author.objects.all(), required=False)

    class Meta:
        model = Tag
    #...
...

Creating Tag List Page #

In the cadmin's views.py, add tag_list() view just after category_delete() view.

TGDB/django_project/cadmin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#...
def category_delete(request, pk):
    #...

# view to add list all tags
@login_required
def tag_list(request):
    if request.user.is_superuser:
        tags = Tag.objects.order_by("-id").all()
    else:
        tags = Tag.objects.filter(author__user__username=request.user.username).order_by("-id")

    tags = helpers.pg_records(request, tags, 5)
    return render(request, 'cadmin/tag_list.html', {'tags': tags})

Create a new template named tag_list.html and add the following code to it:

TGDB/django_project/cadmin/templates/cadmin/tag_list.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
{% extends "cadmin/base_admin.html" %}

{% block title %}
    All Tags - {{ block.super }}
{% endblock %}

{% block main %}

   <div class="main">

        <p>&#187; <a href="{% url 'tag_list' %}">All Tags</a></p>

        <p class="button"><a href="{% url 'tag_add' %}">Add Tag &raquo;</a></p>

        {% if messages %}
            {% for message in messages %}
            <p>{{ message }}</p>
            {% endfor %}
        {% endif %}

        <p class="count">Total Tags: {{ tags.paginator.count }}</p>

            {% for tag in tags %}
                {% if forloop.first     %}
                    <table class="tbl-class">
                        <tr>
                            <th>Name</th>
                            <th>Slug</th>
                            <th>Author</th>
                            <th>Action</th>
                        </tr>
                {% endif %}

                <tr>
                    <td class="post-title">{{ tag.name }}</td>
                    <td>{{ tag.slug }}</td>
                    <td>{{ tag.author.user }}</td>
                    <td>
                        <a href="{% url 'tag_update' tag.id %}">Edit</a> |
                        <a href="{% url 'tag_delete' tag.id %}?next={{ request.get_full_path }}" onclick=" return confirm('Are you sure ?')">Delete</a>
                    </td>
                </tr>

                {% if forloop.last %}
                    </table>
                {% endif %}

            {% empty %}
                No Tags
            {% endfor %}

   </div>

    {% if tags %}

        <div class="pagination">
            <p>
                {% if tags.has_previous %}
                    <a href="?page={{ tags.previous_page_number }}">&lt; Prev</a> |
                {% endif %}

                {% if tags.has_next %}
                    <a href="?page={{ tags.next_page_number }}">Next &gt;</a>
                {% endif %}

                <span>Page {{ tags.number }} of {{ tags.paginator.num_pages }}</span>
            </p>
        </div>

    {% endif %}

{% endblock %}

Next, add the tag_list url pattern at the end of urlpatterns list as follows:

TGDB/django_project/cadmin/urls.py

1
2
3
4
5
6
#...
urlpatterns = [
    #...
    url(r'^category/delete/(?P<pk>[\d]+)/$', views.category_delete, name='category_delete'),
    url(r'^tag/$', views.tag_list, name='tag_list'),
]

Tag Add/Update Page #

In the cadmin's views.py file add tag_add() and tag_update() views as follows:

TGDB/django_project/cadmin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from django.shortcuts import render, redirect, get_object_or_404, reverse, Http404
from blog.forms import PostForm, CategoryForm, TagForm

#...
def tag_list(request):
    #...

# view to add new tag
@login_required
def tag_add(request):

    # If request is POST, create a bound form(form with data)
    if request.method == "POST":
        f = TagForm(request.POST)

        # check whether form is valid or not
        # if the form is valid, save the data to the database
        # and redirect the user back to the add post form

        # If form is invalid show form with errors again
        if f.is_valid():
            # new_category = f.save()
            # new_category = f.save(commit=False)
            # new_category.author = get_user(request)
            # new_category.save()

            if request.POST.get('author') == "" and request.user.is_superuser:
                # if author is not supplied and user is superuser
                new_tag = f.save(commit=False)
                author = Author.objects.get(user__username='staff')
                new_tag.author = author
                new_tag.save()
            elif request.POST.get('author') and request.user.is_superuser:
                # if author is supplied and user is superuser
                new_tag = f.save()
            else:
                # if author not a superuser
                new_tag = f.save(commit=False)
                new_tag.author = Author.objects.get(user__username=request.user.username)
                new_tag.save()

            messages.add_message(request, messages.INFO, 'Tag added')
            return redirect('tag_add')

    # if request is GET the show unbound form to the user
    else:
        f = TagForm()

    return render(request, 'cadmin/tag_add.html', {'form': f})


# view to update tag
@login_required
def tag_update(request, pk):
    tag = get_object_or_404(Tag, pk=pk)

    # If request is POST, create a bound form(form with data)
    if request.method == "POST": # If request is POST, create a bound form
        f = TagForm(request.POST, instance=tag)

        # check whether form is valid or not
        # if the form is valid, save the data to the database
        # and redirect the user back to the tag update form

        # If form is invalid show form with errors again
        if f.is_valid():
            # updated_tag = f.save()

            if request.POST.get('author') == "" and request.user.is_superuser:
                # if author is not supplied and user is superuser
                updated_tag = f.save(commit=False)
                author = Author.objects.get(user__username='staff')
                updated_tag.author = author
                updated_tag.save()
            elif request.POST.get('author') and request.user.is_superuser:
                # if author is supplied and user is superuser
                updated_tag = f.save()
            else:
                # if author not a superuser
                updated_tag = f.save(commit=False)
                updated_tag.author = Author.objects.get(user__username=request.user.username)
                updated_tag.save()

            messages.add_message(request, messages.INFO, 'Tag updated')
            return redirect(reverse('tag_update', args=[tag.id]))

    # if request is GET the show unbound form to the user
    else:
        f = TagForm(instance=tag)

    return render(request, 'cadmin/tag_update.html', {'form': f, 'tag': tag})

Create two new templates tag_add.html and tag_update.html with the following code:

TGDB/django_project/cadmin/templates/cadmin/tag_add.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
{% extends "cadmin/base_admin.html" %}

{% block title %}
    Add New Tag - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="main">

        <p>&#187; <a href="{% url 'tag_list' %}">All Tags</a> &#187; Add Tag</p>

        <h3>Add Tag</h3>

        {% if messages %}
            {% for message in messages %}
            <p>{{ message }}</p>
            {% endfor %}
        {% endif %}

        {{ form.non_field_errors }}

        <form action="" method="post">
            {% csrf_token %}
            <table>
                <tr>
                    <td>{{ form.name.label_tag }}</td>
                    <td>
                        {{ form.name.errors }}
                        {{ form.name }}
                    </td>
                </tr>
                <tr>
                    <td>{{ form.slug.label_tag }}</td>
                    <td>
                        {{ form.slug.errors }}
                        {{ form.slug }}
                    </td>
                </tr>

                {% if request.user.is_superuser %}
                    <tr>
                        <td>{{ form.author.label_tag }}</td>
                        <td>
                            {{ form.author.errors }}
                            {{ form.author }}
                        </td>
                    </tr>
                {% endif %}

                <tr>
                    <td></td>
                    <td><input type="submit" value="Add Tag"></td>
                </tr>
            </table>
        </form>

    </div>

{% endblock %}

TGDB/django_project/cadmin/templates/cadmin/tag_update.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
{% extends "cadmin/base_admin.html" %}

{% block title %}
    Update Tag - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="main">

        <p>&#187; <a href="{% url 'tag_list' %}">All Tags</a> &#187; Update Tag</p>

        <h3>Update Tag</h3>

        {% if messages %}
            {% for message in messages %}
            <p>{{ message }}</p>
            {% endfor %}
        {% endif %}

        {{ form.non_field_errors }}

        <form action="" method="post">
            {% csrf_token %}
            <table>
            <tr>
                <td>{{ form.name.label_tag }}</td>
                <td>
                    {{ form.name.errors }}
                    {{ form.name }}
                </td>
            </tr>
            <tr>
                <td>{{ form.slug.label_tag }}</td>
                <td>
                    {{ form.slug.errors }}
                    {{ form.slug }}
                </td>
            </tr>

            {% if request.user.is_superuser %}
                <tr>
                    <td>{{ form.author.label_tag }}</td>
                    <td>
                        {{ form.author.errors }}
                        {{ form.author }}
                    </td>
                </tr>
            {% endif %}

            <tr>
                <td></td>
                <td><input type="submit" value="Update Tag"></td>
            </tr>

            </table>
        </form>
    </div>

{% endblock %}

Next, add the following two URL patterns at the end of the urlpatterns list as follows:

TGDB/django_project/cadmin/urls.py

1
2
3
4
5
6
7
#...
urlpatters = [
    #...
    url(r'^tag/$', views.tag_list, name='tag_list'),
    url(r'^tag/add/$', views.tag_add, name='tag_add'),
    url(r'^tag/update/(?P<pk>[\d]+)/$', views.tag_update, name='tag_update'),    
]

Deleting Tags #

To delete tag add the following view to the cadmin's views.py file.

TGDB/django_project/cadmin/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#...
def tag_update(request, pk):
    #...

@login_required
def tag_delete(request, pk):
    tag = get_object_or_404(Tag, pk=pk)
    tag.delete()
    next_page = request.GET['next']
    messages.add_message(request, messages.INFO, 'Tag deleted')
    return redirect(next_page)

Then, add the following URL pattern to the urls.py file as follows:

TGDB/django_project/cadmin/urls.py

1
2
3
4
5
6
#...
urlpatterns = [
    #...
    url(r'^tag/update/(?P<pk>[\d]+)/$', views.tag_update, name='tag_update'),
    url(r'^tag/delete/(?P<pk>[\d]+)/$', views.tag_delete, name='tag_delete'),
]

Creating Account Info Page #

In this section, we will create Account Info page, which will the display details of the logged in user. In views.py, add the following view at the end of the file:

TGDB/django_project/cadmin/views.py

1
2
3
4
5
6
7
#...
def tag_delete(request, pk):
   #...

@login_required
def account_info(request):
    return render(request, 'cadmin/account_info.html')

Create a template called account_info.html and add the following code to it:

TGDB/django_project/cadmin/templates/cadmin/account_info.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{% extends "cadmin/base_admin.html" %}

{% block title %}
    All Posts - {{ block.super }}
{% endblock %}

{% block main %}

    <div class="main">

    <p>&#187; <a href="{% url 'account_info' %}">Account Info</a></p>

        <table>
            <tr>
                <td>Username</td>
                <td>{{ request.user.username }}</td>
            </tr>
            <tr>
                <td>Email</td>
                <td>
                    {% if request.user.email %}
                    {{ request.user.email }}
                    {% else %}
                        NA
                    {% endif %}
                </td>
            </tr>
            <tr>
                <td>Date Joined</td>
                <td>{{ request.user.date_joined }}</td>
            </tr>
            <tr>
                <td>Last Login</td>
                <td>{{ request.user.last_login }}</td>
            </tr>
        </table>

    </div>

{% endblock %}

At last, open urls.py and add the following URL pattern at the end of the urlpatterns list :

TGDB/django_project/cadmin/urls.py

1
2
3
4
5
6
#...
urlpatterns = [
    #...
    url(r'^tag/delete/(?P<pk>[\d]+)/$', views.tag_delete, name='tag_delete'),
    url(r'^account-info/$', views.account_info, name='account_info'),
]

To view logged in user details visit http://127.0.0.1:8000/cadmin/account-info/.

Sidebar Links #

We have created all the required pages for our cadmin app. The only thing remain is to add links to the pages in the sidebar. Open base_admin.html and modify the links in the sidebar as follows as follows:

TGDB/django_project/cadmin/templates/cadmin/base_admin.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{# ... #}
<div class="sidebar">
    <ul>
        <li><a href="{% url 'post_list' %}">Post</a></li>
        <li><a href="{% url 'category_list' %}">Category</a></li>
        <li><a href="{% url 'tag_list' %}">Tag</a></li>
        <li><a href="{% url 'account_info' %}">Account Info</a></li>
        <li><a href="{% url 'password_change' %}">Change Password</a></li>
        <li><a target="_blank" href="{{ request.scheme }}://{{ request.get_host }}">View Site</a></li>
        <li><a href="{% url 'logout' %}">Logout</a></li>
        <li></li>
    </ul>
</div>
{# ... #}

Nothing new here except request.get_host function. The request object (HttpRequest) has a method called get_host() which returns the hostname. At this point, cadmin app is fully functional, use it to create some new post, categories, and tags.

In the next lesson, we will learn how to integrate a WYSIWYG editor in Django Admin and our custom built cadmin app.

Note: To checkout this version of the repository type git checkout 36a.