From e8f8a1300dd59604fc962f73a4aa6f0845f9840f Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Sun, 8 Sep 2024 20:57:57 +1000 Subject: [PATCH] Add import and export tool --- README.md | 9 ++++ data/db.sqlite3 | Bin 147456 -> 163840 bytes links/__pycache__/urls.cpython-312.pyc | Bin 1266 -> 1385 bytes links/__pycache__/views.cpython-312.pyc | Bin 9902 -> 13246 bytes links/templates/links/link_list.html | 18 ++++---- links/templates/links/tools.html | 32 +++++++++++++++ links/urls.py | 1 + links/views.py | 52 ++++++++++++++++++++++++ locale/zh_Hans/LC_MESSAGES/django.mo | Bin 1988 -> 2396 bytes locale/zh_Hans/LC_MESSAGES/django.po | 27 ++++++++++++ new_theme/static/css/dist/styles.css | 36 ++++++++++++++++ templates/base.html | 1 + 12 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 links/templates/links/tools.html diff --git a/README.md b/README.md index 7680786..9e704f3 100644 --- a/README.md +++ b/README.md @@ -124,3 +124,12 @@ To run the URL Manager locally: 9. Open your browser and go to `http://127.0.0.1:8000` Remember to keep both the Tailwind CSS build process and the Django development server running while you're developing. + +## Tools + +The URL Manager includes a Tools page with the following features: + +- Export all aliases as a JSON file +- Import aliases from a JSON file + +To access the Tools page, click on the "Tools" link in the navigation bar. diff --git a/data/db.sqlite3 b/data/db.sqlite3 index 91e3d7288c3eddd17e017537faee41d91814af04..ab521e1675b5f94130ae81fb2b0ee13997a7ba8d 100644 GIT binary patch literal 163840 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 IW6xv)06OwZd;kCd diff --git a/links/__pycache__/urls.cpython-312.pyc b/links/__pycache__/urls.cpython-312.pyc index 23349218b26a3b40a502bcbe853419b782398f4c..dfbf32f3ad8d635f1490e068725643815cb62d9c 100644 GIT binary patch delta 226 zcmeyw`I1ZhG%qg~0}#xvznk`+iGkrUhyw$>P{wDUjp{!cCnm^Ri>zT=%?uG`Vn}67 z5lv;zl7cBm&?#aFb?KZ@e3jyw5|d4sZt$^{)&Dp)04TG)fo9F8!@-=-r@`a z$%JL5mKO<3-ofm_B?mN)5r~U_PUd1cYa%hhbh`aS`|HA57lpMh3+rq!zrtdDlZB&0 fZieV}N#l!>#uuytuCN4t5a!}%YUD1G0cr&RWl=VG delta 133 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 Yt^2B$ND=2*@^*3?r%eAK@WNB{r; delta 2387 zcmah~U2GIp6rO3jv)$e9Pxt?)Y`di`PD@)V6(b-N&;VkA0BvQejJrFR?a-N>a_?;U z5d#=Pj7AOK7!zN7GD=MJVKv6Y7+;MK#wU|W6n*hkN$^1*jOW}f3u!?2;oEz^bMCok z&fopI_Q`DIyKp!t!SDT-emcFFz7fgFXKrlTH*bfvuq+vp(Pc&Ks1~(jTFma%dIjgR z;&wtyz*;fX7zJM(mno^b+64o#>S$X`2Ka2R1>JHv5QRlfb5kzs;(GO#_=D-EDT% z$X=7QF(U`Kd7DtlgGzw}+A8Bl9|$LeYy-%OB-oZsUI?iD{1^Eg*E_!Czn0R?xz2o- z5&`GvM&9sG%7gp^{~>vl-}V>fR5R9nq@y^1(h`8zQ*BU6>aNZl>e9j1sh5M9V=78x zHR9@3r|y#Tu5OgwG95#W%?RTFY8Tytddl*-GOaPszotv4fn{9)0|NtB=wq;;JNR$G zL3tAogowP2JE2iI!QTr_Dx)po_n}l?oRVQb(>k&NMF=tePiRcpCboRC=Tc=CE_Wl$ zAS4j>Afy1i;Aw~2detg3M)xAygKUU3s#U_6-k=uUjWpNRWBOKt-M1RZp zVsknXkc((p?e-L#xJ!;fUx2OWhw8RW=F$XTOb*Fo{7N!cz;f`S2C;}sbOwJ_ml*Uk zu(SNLNM)^#qQygEqCo{}w?P<31zvF|LCwNyukldz&-K7Ok_Bt(6ZzZ*Y#;XW11Q(_8O2f1u>=ou% z4XB$2HE9^7C4RfVcm$K}`OY>7y+Dh&_EW+d7DOA9PhUrv2VnO)f2|ZEjFf40Nrw?r zW)SapK+A5P8o1MO`6`}z1>rivs|aYEas=!QHE>@VMZvW{@j;(*cV65}p0FfriUcZP zvpp{$^CANm3&(|vd@7$(-T)1D7IK;6$)V?Wi(D$}b18cG;mLa!-A8zhe>zkMp$)NB zxA5%L{B+1TQ?A#X2^iu`%ykiHFSbS-l?mH{?qyUopQz6m|9om-s8zRXLeZYsj-C&P zF%$0wy~O{T8XQR@9a*!74;nQqtsx$0UqZ8d&!ff5IaHN(FWuIrWP+Tpk`>o<>a6uB z-1T_Lbyq|&i6>3GHewQri6thC=y6fFBGKZr*S%DKckhHxXjo(y{ZQ(YBpj{mEr35d iWm&!@&HNzE+>&ZPN;N*RbrYZ8dMx;-6qGaK9s3t?BMZy` diff --git a/links/templates/links/link_list.html b/links/templates/links/link_list.html index c0bd92a..aa18f2d 100644 --- a/links/templates/links/link_list.html +++ b/links/templates/links/link_list.html @@ -19,10 +19,10 @@ - - - - + - - - + @@ -76,7 +76,9 @@ {{ link.alias }} diff --git a/links/templates/links/tools.html b/links/templates/links/tools.html new file mode 100644 index 0000000..c0252ed --- /dev/null +++ b/links/templates/links/tools.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
+
+
+

{% trans "Tools" %}

+
+
+

{% trans "Export Aliases" %}

+
+ {% csrf_token %} + + + +

{% trans "Import Aliases" %}

+
+ {% csrf_token %} +
+ +
+ + +
+
+
+{% endblock %} diff --git a/links/urls.py b/links/urls.py index 43b081a..3e3964b 100644 --- a/links/urls.py +++ b/links/urls.py @@ -9,5 +9,6 @@ urlpatterns = [ path('delete-selected/', views.delete_selected, name='delete_selected'), path('detail//', views.LinkDetailView.as_view(), name='link_detail'), path('search/', views.SearchView.as_view(), name='search'), + path('tools/', views.ToolsView.as_view(), name='tools'), # Add this line path('/', views.redirect_to_original, name='redirect_to_original'), ] diff --git a/links/views.py b/links/views.py index 786d838..7601980 100644 --- a/links/views.py +++ b/links/views.py @@ -1,5 +1,6 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.views.generic import ListView, CreateView, UpdateView, DeleteView, DetailView, TemplateView +from django.views import View # Add this import from django.urls import reverse_lazy, reverse # Add 'reverse' here from django.db.models import F, Count, Q from django.db.models.functions import TruncDate @@ -13,6 +14,10 @@ from django.utils.translation import gettext as _ from django.core.exceptions import ValidationError from django.http import JsonResponse +from django.http import JsonResponse, HttpResponse +from django.core.serializers.json import DjangoJSONEncoder +from django.utils.dateparse import parse_datetime + class LinkListView(ListView): model = Link template_name = 'links/link_list.html' @@ -146,3 +151,50 @@ class SearchView(TemplateView): context['links'] = links context['query'] = query return context + +class ToolsView(View): + template_name = 'links/tools.html' + + def get(self, request): + return render(request, self.template_name) + + def post(self, request): + if 'export' in request.POST: + links = Link.objects.all().values('alias', 'original_url', 'created_at', 'updated_at') + response = HttpResponse(json.dumps(list(links), cls=DjangoJSONEncoder), content_type='application/json') + response['Content-Disposition'] = 'attachment; filename="links_export.json"' + return response + elif 'import' in request.POST: + try: + json_file = request.FILES['json_file'] + data = json.load(json_file) + + if not isinstance(data, list): + raise ValueError(_("Invalid JSON format. Expected a list of objects.")) + + imported_count = 0 + skipped_count = 0 + for item in data: + if not isinstance(item, dict) or 'alias' not in item: + raise ValueError(_("Invalid item in JSON. Each item must be an object with an 'alias' field.")) + + if not Link.objects.filter(alias=item['alias']).exists(): + Link.objects.create( + alias=item['alias'], + original_url=item.get('original_url', ''), + created_at=parse_datetime(item.get('created_at')) if item.get('created_at') else None, + updated_at=parse_datetime(item.get('updated_at')) if item.get('updated_at') else None + ) + imported_count += 1 + else: + skipped_count += 1 + + messages.success(request, _(f"{imported_count} aliases have been imported successfully. {skipped_count} were skipped.")) + except json.JSONDecodeError: + messages.error(request, _("Invalid JSON file. Please check the file format.")) + except ValueError as e: + messages.error(request, str(e)) + except Exception as e: + messages.error(request, _(f"An error occurred during import: {str(e)}")) + + return render(request, self.template_name) diff --git a/locale/zh_Hans/LC_MESSAGES/django.mo b/locale/zh_Hans/LC_MESSAGES/django.mo index 326b3c816db60a4546b46cf88c9a4476666ff3d0..079e0c2322a71cbec353c5b1af9abbafa6d032ed 100644 GIT binary patch delta 1203 zcmX|w-PZc6irEu zRmu>22%!(kNJ2BEh$8H%ry>an+M5PJ1QA3O{m$LlhI{5W|Cw{(m%LcX5G$y zMJvKqW5*JdDuaWGbhJ8$QpvC$ro%><1TVvU*aM5ql=w8KQVU@& z#J@#tU?2;dpd{J}`P3e|6u3{%AAxT4377^=&5N47P!bQp#qa@?_$erHui#QRqu0Me zC;8Pc22%N7y^zH5B)|!Dn2E0hAdHT$9LGYF;7+nSH{{4^|Q{wkUY&DviBQD3Y?Yqy3G%5mq zPskTiT6#i8<>3SS)n42A|EiYNX0XOykGyoea|8pS$bhr3q^a5P#C9=Gd(QZblRjUq z&PMi7*z5I$Le=3wpmF2ql=EnUVU0{$ZNu|V+vjd|MaS=1Jy+-YhQ9O-%#VzGo4i4N z8Lp`^acZBiI__G%S5!bHK&KqHYDB$y;(kdQDiScF9+7L!G~uyy-bnl56H)FP>* zi*yo+Q6vVTGqJE5#Pf2RaFX{v_ndRTbH97;eTrX4GnIJY*kJ>x8x8rK8-t@Y4A=_{ z!B-fCA20}ge&^CK1Z&|qY=)EO&zrvl_1y-ngS(dB|BYwd0TXR-1OqS+Bk;uH=P-_6 zgbnb<^7j^hhB4yrman0R>O>jzV+pA5l2CPqq1I3T_!;LGm`oB_hgz6}_3#YpkBe}c zu9Tq)esX-R^Wn&EViI7wr z-x(_we=~kTbudO-nYyGg1yyeZ>PIGEMw2;9tiTEUO{% trans "URL Manager" %}
+ + {% trans "Alias" %} {% if current_sort == 'alias' %} @@ -32,8 +32,8 @@ {% endif %} {% trans "Original URL" %} + {% trans "Original URL" %} {% trans "Clicks" %} {% if current_sort == 'clicks' %} @@ -43,7 +43,7 @@ {% endif %} + {% trans "Created At" %} {% if current_sort == 'created_at' %} @@ -53,7 +53,7 @@ {% endif %} + {% trans "Updated At" %} {% if current_sort == 'updated_at' %} @@ -63,7 +63,7 @@ {% endif %} {% trans "Actions" %}{% trans "Actions" %}
- {{ link.original_url }} + {{ link.click_count }} {{ link.created_at|date:"Y-m-d H:i" }}