Revisiting cadmin App

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

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

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

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

Let's start modifying Post Add page.

Modifying Post Add Page #

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

Further, It will not be mandatory for the logged in user (superuser) to select the author, in case he didn't select any author we will assign a special author account (staff) to the post. Okay, let's implement this:

Open post_add.html and modify the file as follows:

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

{% 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 %}
            {% for message in messages %}
            <p>{{ message }}</p>
            {% endfor %}
        {% endif %}

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

        <form action="{% url 'post_add' %}" 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 non superuser account and try creating a new post.

new-add-post-page.png

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 if tag in post_add.html and hit the submit button again in Post add page.

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

...
{#  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:

post_add_page_with_author_field_required_error.png

One way to solve this problem is to create amend our PostForm class which resides in blog's forms.py file as follows.

TGDB/django_project/blog/forms.py

...
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.

>>>
>>> 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

...
@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 know, calling save() method on a ModelForm class saves the object in 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 model, 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 labour login to cadmin app using a superuser account 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 field and hit "Add Post" to create new post.

post-without-selecting-author.png

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

TGDB/django_project/cadmin/views.py

...
# 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.

post-with-author-selected.png

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

TGDB/django_project/cadmin/views.py

...
# 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 non superuser account. And create a new post again by visiting http://127.0.0.1:1000/cadmin/post/add/ page.

post-add-form.png

As you can see the logged in user is not allowed to select author. Create a post and hit "Add Post" button. This action will execute else block of the code in the post_add() view:

TGDB/django_project/cadmin/views.py

...
# 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

...
@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

{% extends "cadmin/base_admin.html" %}

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

{% block main %}

    <div class="main">
        <p>&#187; <a href="">All Posts</a> &#187; Post Update</p>

        <h3>Post Update</h3>

        {% if messages %}
            {% for message in messages %}
            <p>{{ message }}</p>
            {% endfor %}
        {% 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 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

...
from django_project.helpers import generate_activation_key, pg_records

...
@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 = 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 the it will only display posts created by himself.

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

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

{% 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 logged in users to show 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

...
urlpatterns = [
    ...
    url(r'^$', views.home, name='home'),
    ...
]

Update the above URL pattern as follows:

TGDB/django_project/cadmin/urls.py

...
urlpatterns = [
    ...
    url(r'^$', views.post_list, name='post_list'),
    ...
]

If you try to view post list page (http://127.0.0.1:8000/cadmin/) at this point, you will get an error as follows:

noreverse-exception-at-cadmin-homepage.png

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

...

@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

...
urlpatterns = [
    ...
    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:

cadmin-home-page.png

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

...

@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 = 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

{% 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 as follows:

TGDB/django_project/cadmin/urls.py

...
urlpatterns = [
    ...
    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

...
class CategoryForm(forms.ModelForm):
    author = forms.ModelChoiceField(queryset=Author.objects.all(), required=False)

    ...

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

TGDB/django_project/cadmin/views.py

...
from blog.forms import PostForm, CategoryForm
...

# 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 the following code:

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

{% 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

{% 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

...
urlpatterns = [
    ...
    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

...

@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

...
urlpatterns = [
    ...
    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

...
class TagForm(forms.ModelForm):
    author = forms.ModelChoiceField(queryset=Author.objects.all(), required=False)

...

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

...

# 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 = 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

{% 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

...
urlpatterns = [
    ...
    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

...

# 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

{% 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

{% 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

...
urlpatters = [
    ...
    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

...

@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

...
urlpatterns = [
    ...
    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 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

...

@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

{% 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

...
urlpatterns = [
    ...
    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/.

account-info-page-FXUATR.png

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 sidebar as follows as follows:

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

...
<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.