Add basis to work on articles
This commit is contained in:
parent
ec31d838c1
commit
75e1bec1d0
13 changed files with 324 additions and 4 deletions
|
@ -1,6 +1,37 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.contrib.admin import register
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
|
||||||
from .models import User
|
from .models import Article, User
|
||||||
|
|
||||||
admin.site.register(User, UserAdmin)
|
admin.site.register(User, UserAdmin)
|
||||||
|
|
||||||
|
|
||||||
|
@register(Article)
|
||||||
|
class ArticleAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"title",
|
||||||
|
"status",
|
||||||
|
"author",
|
||||||
|
"created_at",
|
||||||
|
"published_at",
|
||||||
|
"updated_at",
|
||||||
|
]
|
||||||
|
list_display_links = ["title"]
|
||||||
|
list_filter = ["status"]
|
||||||
|
date_hierarchy = "created_at"
|
||||||
|
fieldsets = [
|
||||||
|
(
|
||||||
|
"Metadata",
|
||||||
|
{
|
||||||
|
"fields": (
|
||||||
|
"title",
|
||||||
|
"status",
|
||||||
|
("created_at", "published_at", "updated_at"),
|
||||||
|
"author",
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
("Content", {"fields": ("content",)}),
|
||||||
|
]
|
||||||
|
readonly_fields = ["created_at", "updated_at"]
|
||||||
|
|
130
articles/migrations/0001_initial.py
Normal file
130
articles/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
# Generated by Django 3.1 on 2020-08-14 12:56
|
||||||
|
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("auth", "0012_alter_user_first_name_max_length"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="User",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||||
|
(
|
||||||
|
"last_login",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True, null=True, verbose_name="last login"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_superuser",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||||
|
verbose_name="superuser status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"username",
|
||||||
|
models.CharField(
|
||||||
|
error_messages={
|
||||||
|
"unique": "A user with that username already exists."
|
||||||
|
},
|
||||||
|
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||||
|
max_length=150,
|
||||||
|
unique=True,
|
||||||
|
validators=[
|
||||||
|
django.contrib.auth.validators.UnicodeUsernameValidator()
|
||||||
|
],
|
||||||
|
verbose_name="username",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"first_name",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=150, verbose_name="first name"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"last_name",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=150, verbose_name="last name"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
models.EmailField(
|
||||||
|
blank=True, max_length=254, verbose_name="email address"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_staff",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Designates whether the user can log into this admin site.",
|
||||||
|
verbose_name="staff status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_active",
|
||||||
|
models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||||
|
verbose_name="active",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"date_joined",
|
||||||
|
models.DateTimeField(
|
||||||
|
default=django.utils.timezone.now, verbose_name="date joined"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"groups",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.Group",
|
||||||
|
verbose_name="groups",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user_permissions",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Specific permissions for this user.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.Permission",
|
||||||
|
verbose_name="user permissions",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "user",
|
||||||
|
"verbose_name_plural": "users",
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
managers=[("objects", django.contrib.auth.models.UserManager()),],
|
||||||
|
),
|
||||||
|
]
|
50
articles/migrations/0002_article.py
Normal file
50
articles/migrations/0002_article.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Generated by Django 3.1 on 2020-08-14 13:16
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Article",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("title", models.CharField(max_length=255)),
|
||||||
|
("content", models.TextField()),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
models.CharField(
|
||||||
|
choices=[("draft", "Draft"), ("published", "Published")],
|
||||||
|
default="draft",
|
||||||
|
max_length=15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("published_at", models.DateTimeField(null=True)),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"author",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={"ordering": ["-published_at"],},
|
||||||
|
),
|
||||||
|
]
|
18
articles/migrations/0003_auto_20200814_1522.py
Normal file
18
articles/migrations/0003_auto_20200814_1522.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 3.1 on 2020-08-14 13:22
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("articles", "0002_article"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="article",
|
||||||
|
name="published_at",
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,35 @@
|
||||||
|
import markdown
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Article(models.Model):
|
||||||
|
DRAFT = "draft"
|
||||||
|
PUBLISHED = "published"
|
||||||
|
STATUS_CHOICES = [
|
||||||
|
(DRAFT, "Draft"),
|
||||||
|
(PUBLISHED, "Published"),
|
||||||
|
]
|
||||||
|
title = models.CharField(max_length=255)
|
||||||
|
content = models.TextField()
|
||||||
|
status = models.CharField(max_length=15, choices=STATUS_CHOICES, default=DRAFT)
|
||||||
|
published_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
author = models.ForeignKey(User, on_delete=models.PROTECT)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["-published_at"]
|
||||||
|
|
||||||
|
def get_abstract(self):
|
||||||
|
md = markdown.Markdown(extensions=["extra"])
|
||||||
|
html = md.convert(self.content)
|
||||||
|
return html.split("<!--more-->")[0]
|
||||||
|
|
||||||
|
def get_formatted_content(self):
|
||||||
|
md = markdown.Markdown(extensions=["extra"])
|
||||||
|
return md.convert(self.content)
|
||||||
|
|
6
articles/static/style.css
Normal file
6
articles/static/style.css
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.content {
|
||||||
|
max-width: 640px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
12
articles/templates/articles/article_detail.html
Normal file
12
articles/templates/articles/article_detail.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends 'articles/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ article.title }}</h1>
|
||||||
|
<p>By: {{ article.author }}</p>
|
||||||
|
<p>Published at: {{ article.published_at }}</p>
|
||||||
|
<p>Updated at: {{ article.updated_at }}</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ article.get_formatted_content|safe }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
12
articles/templates/articles/article_list.html
Normal file
12
articles/templates/articles/article_list.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends 'articles/base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Articles list</h1>
|
||||||
|
{% for article in articles %}
|
||||||
|
<article>
|
||||||
|
<h2>{{ article.title }}</h2>
|
||||||
|
<p>{{ article.get_abstract|safe }}</p>
|
||||||
|
<p><a href="{% url 'article-detail' pk=article.pk %}">Read more</a></p>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
15
articles/templates/articles/base.html
Normal file
15
articles/templates/articles/base.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Gabnotes</title>
|
||||||
|
<link rel="stylesheet" href="{% static 'style.css' %}" type="text/css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,3 +1,14 @@
|
||||||
from django.shortcuts import render
|
from django.views import generic
|
||||||
|
|
||||||
# Create your views here.
|
from articles.models import Article
|
||||||
|
|
||||||
|
|
||||||
|
class ArticlesListView(generic.ListView):
|
||||||
|
model = Article
|
||||||
|
paginate_by = 15
|
||||||
|
context_object_name = "articles"
|
||||||
|
|
||||||
|
|
||||||
|
class ArticleDetailView(generic.DetailView):
|
||||||
|
model = Article
|
||||||
|
context_object_name = "article"
|
||||||
|
|
|
@ -37,6 +37,7 @@ INSTALLED_APPS = [
|
||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
"articles",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -99,7 +100,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
|
||||||
LANGUAGE_CODE = "en-us"
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
TIME_ZONE = "UTC"
|
TIME_ZONE = "Europe/Paris"
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,10 @@ Including another URLconf
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
from articles import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
|
path("", views.ArticlesListView.as_view(), name="articles-list"),
|
||||||
|
path("<int:pk>", views.ArticleDetailView.as_view(), name="article-detail"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
django==3.1
|
django==3.1
|
||||||
pre-commit==2.6.0
|
pre-commit==2.6.0
|
||||||
|
markdown==3.2.2
|
||||||
|
|
Reference in a new issue