Skip to content

Commit

Permalink
Add CRUD operations to items and categories for admin users (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
TrinhTrungDung committed Aug 12, 2020
1 parent 466d330 commit fe4d92b
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 47 deletions.
21 changes: 21 additions & 0 deletions README.md
Expand Up @@ -7,6 +7,7 @@ You must first create a dotenv file `.env` in the project directory, the file sh
```
DJANGO_SECRET=<django_secret_key>
DEBUG=True
LIST_PER_PAGE=10
POSTGRES_DB=<postgres_db>
POSTGRES_USER=<postgres_user>
POSTGRES_PASSWORD=<postgres_password>
Expand All @@ -19,6 +20,14 @@ Either type each commands respectively in the `startserver.sh` file in the scrip
./scripts/startserver.sh
```

After that, you need to create a super user account to access admin dashboard by following command:

```
./scripts/compose.sh run web python manage.py createsuperuser
```

Just follow the instruction to create your own super user credentials.

That's it, you can check the server up and running in your local machine.

## Shutdown the container
Expand All @@ -41,6 +50,18 @@ In order to grab all items from given feed URLs, you can use the following comma

Note: The urls must be separated by a comma, the items will then be printed on the terminal.

### 2. Configure pagination value

You can easily configure the pagination value by adding the following variable in your `.env` file:

```
LIST_PER_PAGE=10
```

### 3. Items/Categories CRUD

In order to make any operations manually to items and categories, you can use the super user credentials to login the admin site `http://localhost:8000/admin` on your local machine and make actions. You can also search the items based on category title by using the search bar inside the `Items` navigation menu item.

## Troubleshooting

If you are facing any problems while running any scripts, for example a permission error, you can easily grant the permission for the script by typing the following command:
Expand Down
2 changes: 2 additions & 0 deletions config/settings.py
Expand Up @@ -69,6 +69,8 @@
},
]

LIST_PER_PAGE = int(env("LIST_PER_PAGE"))

WSGI_APPLICATION = 'config.wsgi.application'


Expand Down
17 changes: 17 additions & 0 deletions feeds/admin.py
@@ -0,0 +1,17 @@
from django.conf import settings
from django.contrib import admin

from .forms import ItemAdminForm
from .models import Item, Category


@admin.register(Item)
class ItemAdmin(admin.ModelAdmin):
list_per_page = settings.LIST_PER_PAGE
form = ItemAdminForm
search_fields = ('categories__title',)


@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_per_page = settings.LIST_PER_PAGE
36 changes: 36 additions & 0 deletions feeds/forms.py
@@ -0,0 +1,36 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.admin.widgets import FilteredSelectMultiple
from .models import Item, Category


class ItemAdminForm(forms.ModelForm):
categories = forms.ModelMultipleChoiceField(
queryset=Category.objects.all(),
required=False,
widget=FilteredSelectMultiple(
verbose_name=_("Categories"),
is_stacked=False
)
)

class Meta:
model = Item
fields = '__all__'

def __init__(self, *args, **kwargs):
super(ItemAdminForm, self).__init__(*args, **kwargs)

if self.instance.id:
self.fields["categories"].initial = self.instance.categories.all()

def save(self, commit=True):
article = super(ItemAdminForm, self).save(commit=False)
if self.cleaned_data["categories"]:
article.save()
article.categories.set(self.cleaned_data["categories"])
if commit:
article.save()
article.save_m2m()

return article
72 changes: 25 additions & 47 deletions feeds/management/commands/grabfeeds.py
Expand Up @@ -4,44 +4,13 @@
import xml.etree.ElementTree as ET
import html
from datetime import datetime
from typing import List
import logging
from ...models import Item, Category


logger = logging.getLogger(__name__)


class Item(object):

def __init__(self, title: str = "", description: str = "",
link: str = "", categories: List[str] = [],
comments: str = "", pubDate: str = "", guid: int = 0):
self.title = title
self.description = description
self.link = link
self.categories = categories
self.comments = comments
self.pubDate = pubDate
self.guid = guid

def __repr__(self):
guid = categories = ""
if self.guid:
guid = str(self.guid)
if self.categories:
categories = "\n\t+ "\
.join(category for category in self.categories)
categories = f"\n\t+ {categories}"
return (f"Article:\n"
f"- Title: {self.title}\n"
f"- Description: {self.description}\n"
f"- Link: {self.link}\n"
f"- Categories: {categories}\n"
f"- Comments: {self.comments}\n"
f"- Published Date: {self.pubDate}\n"
f"- GUID: {guid}\n")


class Command(BaseCommand):
help = "Grab items from the specified feeds URLs"

Expand All @@ -55,33 +24,42 @@ def handle(self, *args, **kwargs):
raise CommandError("URL should not be empty")
urls = urls.split(",")
for url in urls:
articles = []
try:
url = urlparse(url).geturl()
response = requests.get(url)
root = ET.fromstring(response.content)
for item in root.findall("./channel/item"):
article = Item()
for ch in item.getchildren():
if ch.tag == "category":
tag = ch.tag
if tag == "category":
article.save()
categories = ch.text.split("/")
setattr(article, "categories", categories)
for title in categories:
category, created = Category.objects.get_or_create(
title=title)
if created:
successMessage = (f"Successfully create category "
f"{category} with URL: {url}")
self.stdout.write(
self.style.SUCCESS(successMessage))
logger.info(successMessage)
article.categories.add(category)
else:
attribute = ch.text
if ch.tag == "description":
attribute = html.escape(ch.text)
elif ch.tag == "pubDate":
attribute = datetime.strptime(
attribute, "%a, %d %b %Y %H:%M:%S %z")
elif ch.tag == "guid":
attribute = int(attribute)
setattr(article, ch.tag, attribute)
articles.append(article)
if tag == "description":
article.description = html.escape(ch.text)
elif tag == "pubDate":
article.pub_date = datetime.strptime(
ch.text, "%a, %d %b %Y %H:%M:%S %z")
elif tag == "guid":
article.guid = int(ch.text)
else:
setattr(article, tag, ch.text)
article.save()
self.stdout.write(self.style.SUCCESS(f"{article}"))
successMessage = f"Successfully grab items with URL: {url}"
self.stdout.write(self.style.SUCCESS(successMessage))
logger.info(successMessage)
for article in articles:
self.stdout.write(self.style.SUCCESS(f"{article}"))
except requests.exceptions.MissingSchema:
error = f"Invalid URL: {url}"
self.stderr.write(self.style.ERROR(error))
Expand Down
42 changes: 42 additions & 0 deletions feeds/migrations/0001_initial.py
@@ -0,0 +1,42 @@
# Generated by Django 3.1 on 2020-08-12 07:08

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Item',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200, verbose_name='Title')),
('description', models.TextField(verbose_name='Description')),
('link', models.URLField(verbose_name='Link')),
('comments', models.URLField(verbose_name='Comments Link')),
('pub_date', models.DateTimeField(blank=True, null=True, verbose_name='Published Date')),
('guid', models.IntegerField(default=0, verbose_name='GUID')),
],
options={
'verbose_name_plural': 'Items',
'db_table': 'feeds_items',
},
),
migrations.CreateModel(
name='Category',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=50, unique=True, verbose_name='Category')),
('items', models.ManyToManyField(related_name='categories', to='feeds.Item')),
],
options={
'verbose_name_plural': 'Categories',
'db_table': 'feeds_categories',
},
),
]
31 changes: 31 additions & 0 deletions feeds/models.py
@@ -0,0 +1,31 @@
from django.db import models


class Item(models.Model):
title = models.CharField(verbose_name="Title", max_length=200)
description = models.TextField(verbose_name="Description")
link = models.URLField(verbose_name="Link")
comments = models.URLField(verbose_name="Comments Link")
pub_date = models.DateTimeField(
verbose_name="Published Date", null=True, blank=True)
guid = models.IntegerField(verbose_name="GUID", default=0)

class Meta:
db_table = "feeds_items"
verbose_name_plural = "Items"

def __str__(self):
return f"{self.title}"


class Category(models.Model):
title = models.CharField(verbose_name="Category",
max_length=50, unique=True)
items = models.ManyToManyField(Item, related_name="categories")

class Meta:
db_table = "feeds_categories"
verbose_name_plural = "Categories"

def __str__(self):
return f"{self.title}"

0 comments on commit fe4d92b

Please sign in to comment.