From 07d9bac5c58147aeed14358c1efc8ea888f6ee39 Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Mon, 8 Jun 2020 20:33:29 +0200 Subject: [PATCH] multiple improvements --- acronomy/settings.py | 3 +- acros/admin.py | 10 ++--- acros/forms.py | 6 +++ .../management/commands/refetch_wikipedia.py | 2 + acros/migrations/0028_auto_20200608_1705.py | 37 ++++++++++++++++++ acros/migrations/0029_historicaltag.py | 36 +++++++++++++++++ acros/migrations/0030_auto_20200608_1823.py | 18 +++++++++ acros/models/Acronym.py | 7 +++- acros/models/Tag.py | 2 + acros/sitemaps.py | 14 +++++++ acros/templates/acros/add.html | 21 ++++++++-- acros/templates/acros/detail.html | 17 ++++++-- acros/templates/acros/edit.html | 10 +++-- acros/templates/acros/overview.html | 32 ++++++++++----- acros/templates/acros/tagacro.html | 18 +++++---- acros/urls.py | 18 ++++++--- static/app.js | 4 +- static/images/{ads.ico => adees.ico} | Bin static/images/{ads.png => adees.png} | Bin static/libs/codemirror.js | 1 + static/libs/markdown.js | 1 + static/scss/main.scss | 1 + templates/base.html | 6 +++ 23 files changed, 222 insertions(+), 42 deletions(-) create mode 100644 acros/migrations/0028_auto_20200608_1705.py create mode 100644 acros/migrations/0029_historicaltag.py create mode 100644 acros/migrations/0030_auto_20200608_1823.py create mode 100644 acros/sitemaps.py rename static/images/{ads.ico => adees.ico} (100%) rename static/images/{ads.png => adees.png} (100%) create mode 120000 static/libs/codemirror.js create mode 120000 static/libs/markdown.js diff --git a/acronomy/settings.py b/acronomy/settings.py index b1918fb..6eedf8d 100644 --- a/acronomy/settings.py +++ b/acronomy/settings.py @@ -32,6 +32,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.sitemaps', 'simple_history', 'debug_toolbar', 'rest_framework', @@ -122,7 +123,7 @@ INTERNAL_IPS = [ '127.0.0.1', ] -TAGGIT_CASE_INSENSITIVE = True +SIMPLE_HISTORY_FILEFIELD_TO_CHARFIELD = True SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True diff --git a/acros/admin.py b/acros/admin.py index f73ee48..5ac163a 100644 --- a/acros/admin.py +++ b/acros/admin.py @@ -24,7 +24,7 @@ class WikiInline(OwnInline): fields = ["title"] -class TagAdmin(admin.ModelAdmin): +class TagAdmin(SimpleHistoryAdmin): # prepopulated_fields = {'slug': ('name',)} readonly_fields = ["slug"] @@ -36,20 +36,20 @@ class AcronymAdmin(SimpleHistoryAdmin): filter_horizontal = ["tags"] readonly_fields = ["slug"] list_display = ["name", "full_name"] - list_filter = ["tags"] + list_filter = ["tags", "modified_date", "created_date"] save_on_top = True -class PaperAdmin(admin.ModelAdmin): +class PaperAdmin(SimpleHistoryAdmin): date_hierarchy = "pubdate" list_display = ["title", "authors"] -class LinkAdmin(admin.ModelAdmin): +class LinkAdmin(SimpleHistoryAdmin): readonly_fields = ["host"] -class WikipediaAdmin(admin.ModelAdmin): +class WikipediaAdmin(SimpleHistoryAdmin): readonly_fields = ["thumbnail_height", "thumbnail_width"] diff --git a/acros/forms.py b/acros/forms.py index c5c0e92..050528e 100644 --- a/acros/forms.py +++ b/acros/forms.py @@ -11,6 +11,11 @@ class TagWidget(TextInput): value = edit_string_for_tags(value) return super().format_value(value) + def __init__(self, *args, **kwargs): + super(TagWidget, self).__init__(*args, **kwargs) + attrs = {"class": "taginput"} + self.attrs.update(attrs) + class TagField(CharField): """ @@ -54,6 +59,7 @@ class AddForm(ModelForm): fields = ['name', 'full_name', "description_md", "tags"] + class AddWikipediaForm(ModelForm): tags = TagField() diff --git a/acros/management/commands/refetch_wikipedia.py b/acros/management/commands/refetch_wikipedia.py index 9536283..72b3f1d 100644 --- a/acros/management/commands/refetch_wikipedia.py +++ b/acros/management/commands/refetch_wikipedia.py @@ -1,4 +1,5 @@ from django.core.management.base import BaseCommand +from simple_history.utils import update_change_reason from acros.models import WikipediaLink @@ -12,4 +13,5 @@ class Command(BaseCommand): if link.fetched: self.stdout.write(link.title) link.fetched = False + update_change_reason(link, "refetch_wikipedia command") link.save() diff --git a/acros/migrations/0028_auto_20200608_1705.py b/acros/migrations/0028_auto_20200608_1705.py new file mode 100644 index 0000000..505a352 --- /dev/null +++ b/acros/migrations/0028_auto_20200608_1705.py @@ -0,0 +1,37 @@ +# Generated by Django 3.0.6 on 2020-06-08 17:05 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('acros', '0027_auto_20200608_1521'), + ] + + operations = [ + migrations.AddField( + model_name='acronym', + name='created_date', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='acronym', + name='modified_date', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='historicalacronym', + name='created_date', + field=models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False), + preserve_default=False, + ), + migrations.AddField( + model_name='historicalacronym', + name='modified_date', + field=models.DateTimeField(blank=True, default=django.utils.timezone.now, editable=False), + preserve_default=False, + ), + ] diff --git a/acros/migrations/0029_historicaltag.py b/acros/migrations/0029_historicaltag.py new file mode 100644 index 0000000..359db13 --- /dev/null +++ b/acros/migrations/0029_historicaltag.py @@ -0,0 +1,36 @@ +# Generated by Django 3.0.6 on 2020-06-08 18:19 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('acros', '0028_auto_20200608_1705'), + ] + + operations = [ + migrations.CreateModel( + name='HistoricalTag', + fields=[ + ('id', models.IntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('slug', models.CharField(editable=False, max_length=100)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField()), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical tag', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/acros/migrations/0030_auto_20200608_1823.py b/acros/migrations/0030_auto_20200608_1823.py new file mode 100644 index 0000000..3797b80 --- /dev/null +++ b/acros/migrations/0030_auto_20200608_1823.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-06-08 18:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('acros', '0029_historicaltag'), + ] + + operations = [ + migrations.AlterField( + model_name='historicalwikipedialink', + name='thumbnail', + field=models.CharField(blank=True, max_length=100), + ), + ] diff --git a/acros/models/Acronym.py b/acros/models/Acronym.py index 60ce08d..83c2c24 100644 --- a/acros/models/Acronym.py +++ b/acros/models/Acronym.py @@ -13,8 +13,10 @@ class Acronym(models.Model): slug = models.SlugField(null=False, unique=True) description_md = models.TextField(blank=True) description_html = models.TextField(editable=False) - history = HistoricalRecords() + history = HistoricalRecords(excluded_fields=["created_date"]) tags = models.ManyToManyField(Tag, related_name="acronyms") + created_date = models.DateTimeField(auto_now_add=True) + modified_date = models.DateTimeField(auto_now=True) def save(self, *args, **kwargs): self.description_html = md_to_html(self.description_md) @@ -28,5 +30,8 @@ class Acronym(models.Model): def get_absolute_url(self): return reverse('detail', args=[str(self.slug)]) + def first_letter(self): + return self.name and self.name[0] or '' + class Meta: ordering = ["name"] diff --git a/acros/models/Tag.py b/acros/models/Tag.py index 9992120..97b96b1 100644 --- a/acros/models/Tag.py +++ b/acros/models/Tag.py @@ -1,11 +1,13 @@ from django.db import models from django.urls import reverse from django.utils.text import slugify +from simple_history.models import HistoricalRecords class Tag(models.Model): name = models.CharField(max_length=100) slug = models.CharField(max_length=100, editable=False) + history = HistoricalRecords() def __str__(self): return self.name diff --git a/acros/sitemaps.py b/acros/sitemaps.py new file mode 100644 index 0000000..e2b9362 --- /dev/null +++ b/acros/sitemaps.py @@ -0,0 +1,14 @@ +from django.contrib.sitemaps import Sitemap + +from .models import Acronym + + +class AcronymSitemap(Sitemap): + changefreq = "weekly" + priority = 0.8 + + def items(self): + return Acronym.objects.all() + + def lastmod(self, obj: Acronym): + return obj.modified_date diff --git a/acros/templates/acros/add.html b/acros/templates/acros/add.html index 830b12f..3c144ee 100644 --- a/acros/templates/acros/add.html +++ b/acros/templates/acros/add.html @@ -1,10 +1,23 @@ {% extends 'base.html' %} +{% load bootstrap4 %} +{% load static %} + +{% block heading %} +

Add new Acronym

+{% endblock %} {% block content %} -
{% csrf_token %} - {{ form.as_p }} - + + {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + + {% endbuttons %}
- +{% endblock %} + +{% block extra_head %} + + {% endblock %} diff --git a/acros/templates/acros/detail.html b/acros/templates/acros/detail.html index 3bae070..75c2488 100644 --- a/acros/templates/acros/detail.html +++ b/acros/templates/acros/detail.html @@ -1,9 +1,16 @@ {% extends 'base.html' %} {% load static %} -{% block content %} + +{% block title %}{{ acro.name }}: {{ acro.full_name }} ‐ Acronomy{% endblock %} + +{% block heading %}

{{ acro.name }}

{{ acro.full_name }}

+ +{% endblock %} + +{% block content %} {% if acro.description_html %}
{{ acro.description_html|safe }} @@ -64,7 +71,7 @@

{{ paper.authors }}

#} {# {% endif %}#}
- Admin-Edit - {# Edit#} + {% if user.is_staff %} + Admin-Edit + {% endif %} + Edit {% endblock %} diff --git a/acros/templates/acros/edit.html b/acros/templates/acros/edit.html index a733b99..8ce8aa4 100644 --- a/acros/templates/acros/edit.html +++ b/acros/templates/acros/edit.html @@ -2,6 +2,11 @@ {% load static %} {% load bootstrap4 %} +{% block heading %} +

Edit "{{ form.name.value }}"

+{% endblock %} + + {% block content %}
@@ -15,7 +20,6 @@ {% endblock %} {% block extra_head %} - - - + + {% endblock %} diff --git a/acros/templates/acros/overview.html b/acros/templates/acros/overview.html index 358ce12..169a502 100644 --- a/acros/templates/acros/overview.html +++ b/acros/templates/acros/overview.html @@ -1,13 +1,25 @@ {% extends 'base.html' %} -{% block content %} -
- - {% for acro in acros %} -
{{ acro.name }}
-
{{ acro.full_name }}
- - - {% endfor %} -
+{% block heading %} +

All Acronyms

+{% endblock %} + +{% block content %} + {% regroup acros by first_letter as letter_acros %} + + + + {% for letter in letter_acros %} +

{{ letter.grouper }}

+
+ {% for acro in letter.list %} +
{{ acro.name }}
+
{{ acro.full_name }}
+ {% endfor %} +
+ {% endfor %} {% endblock %} diff --git a/acros/templates/acros/tagacro.html b/acros/templates/acros/tagacro.html index 6eed35f..846e55d 100644 --- a/acros/templates/acros/tagacro.html +++ b/acros/templates/acros/tagacro.html @@ -1,10 +1,14 @@ {% extends 'base.html' %} -{% block content %} -
- {% for acro in acros %} -
{{ acro.name }}
-
{{ acro.full_name }}
- {% endfor %} -
+ +{% block heading %} +

Acronyms tagged "{{ request.resolver_match.kwargs.slug }}"

+{% endblock %} +{% block content %} +
+ {% for acro in acros %} +
{{ acro.name }}
+
{{ acro.full_name }}
+ {% endfor %} +
{% endblock %} diff --git a/acros/urls.py b/acros/urls.py index 6c51e01..af68235 100644 --- a/acros/urls.py +++ b/acros/urls.py @@ -1,24 +1,32 @@ from django.conf.urls.static import static +from django.contrib.sitemaps.views import sitemap from django.urls import path, include +from django.views.decorators.cache import cache_page from django.views.generic import RedirectView from rest_framework import routers from acronomy import settings from . import views +from .sitemaps import AcronymSitemap router = routers.DefaultRouter() router.register(r'acronym', views.AcronymViewSet) router.register(r'tag', views.TagViewSet) +sitemaps = { + "acronyms": AcronymSitemap +} + urlpatterns = [ path('', include('django.contrib.auth.urls')), + path("sitemap.xml", cache_page(60 * 15)(sitemap), {"sitemaps": sitemaps}, name="sitemap"), path('api/', include(router.urls)), path('', views.IndexView.as_view(), name='index'), - path('acro', RedirectView.as_view(pattern_name="overview")), - path('acro/add', views.AddView.as_view(), name="add"), - path('acro/', views.DetailView.as_view(), name='detail'), - path('acro//edit', views.EditView.as_view(), name='edit'), - path('acros', views.OverView.as_view(), name='overview'), + path('acronym', RedirectView.as_view(pattern_name="overview")), + path('acronym/add', views.AddView.as_view(), name="add"), + path('acronym/', views.DetailView.as_view(), name='detail'), + path('acronym//edit', views.EditView.as_view(), name='edit'), + path('acronyms', views.OverView.as_view(), name='overview'), path('tags', views.TagListView.as_view(), name='tags'), path('tag', RedirectView.as_view(pattern_name="tags")), path('tag/', views.TagAcroView.as_view(), name='tag'), diff --git a/static/app.js b/static/app.js index 880c901..085070b 100644 --- a/static/app.js +++ b/static/app.js @@ -31,11 +31,12 @@ new Autocomplete('#autocomplete', { // a new window onSubmit: result => { console.log(result) - window.location = "/acro/" + result.slug + window.location = "/acronym/" + result.slug }, autoSelect: true, }) const input = document.querySelector('input[name="tags"]') +input.classList.remove("form-control") document.querySelector("form").addEventListener("submit", function () { const list = JSON.parse(input.value).map(function (item) { @@ -75,4 +76,3 @@ const myCodeMirror = CodeMirror.fromTextArea( lineNumbers: true, } ); - diff --git a/static/images/ads.ico b/static/images/adees.ico similarity index 100% rename from static/images/ads.ico rename to static/images/adees.ico diff --git a/static/images/ads.png b/static/images/adees.png similarity index 100% rename from static/images/ads.png rename to static/images/adees.png diff --git a/static/libs/codemirror.js b/static/libs/codemirror.js new file mode 120000 index 0000000..93ef625 --- /dev/null +++ b/static/libs/codemirror.js @@ -0,0 +1 @@ +../../node_modules/codemirror/lib/codemirror.js \ No newline at end of file diff --git a/static/libs/markdown.js b/static/libs/markdown.js new file mode 120000 index 0000000..3094843 --- /dev/null +++ b/static/libs/markdown.js @@ -0,0 +1 @@ +../../node_modules/codemirror/mode/markdown/markdown.js \ No newline at end of file diff --git a/static/scss/main.scss b/static/scss/main.scss index 2b39f9e..66b0943 100644 --- a/static/scss/main.scss +++ b/static/scss/main.scss @@ -4,6 +4,7 @@ @import "node_modules/@trevoreyre/autocomplete-js/dist/style"; @import "node_modules/@yaireo/tagify/dist/tagify"; +@import "node_modules/codemirror/lib/codemirror"; @import "node_modules/bootstrap/scss/functions"; @import "node_modules/bootstrap/scss/variables"; diff --git a/templates/base.html b/templates/base.html index 2d70fa2..a8be97b 100644 --- a/templates/base.html +++ b/templates/base.html @@ -21,7 +21,13 @@
Hello {{ user.get_username }}, Log out
+ {% else %} +
+ Log in +
{% endif %} + {% block heading %} + {% endblock %}