From 94589acedbf8a72fffc1a66994469ea90e7baeed Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Wed, 13 Nov 2024 22:50:44 +1100 Subject: [PATCH] To be able to delete a screenshot --- README.md | 4 +- data/db.sqlite3 | Bin 221184 -> 241664 bytes .../0016_migrate_existing_screenshots.py | 31 +++++ .../0017_remove_old_screenshot_fields.py | 14 +++ links/migrations/0018_page_screenshot_path.py | 18 +++ links/models.py | 8 +- links/page_views.py | 34 ++++++ links/tasks.py | 14 ++- links/templates/links/page_detail.html | 115 +++++++++++++----- links/templates/links/page_list.html | 26 ++-- 10 files changed, 216 insertions(+), 48 deletions(-) create mode 100644 links/migrations/0016_migrate_existing_screenshots.py create mode 100644 links/migrations/0017_remove_old_screenshot_fields.py create mode 100644 links/migrations/0018_page_screenshot_path.py diff --git a/README.md b/README.md index 94e9d56..d93a459 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,8 @@ A URL management tool that helps you organize and access your links efficiently. 3. Build and start services: ```bash - ./manage-docker.sh build - ./manage-docker.sh start + ./docker.sh build + ./docker.sh start ``` The application will be available at `http://localhost:8000` diff --git a/data/db.sqlite3 b/data/db.sqlite3 index 85a1464f86676769fd0f8ade051141f94f13fbb3..a8bc1c81816f2ac8f6fd64a8f6a5718ed7457d50 100644 GIT binary patch delta 4369 zcmai1e{2)i9e?-EzhmdwvGc=m>Nr1u1d_|WyLZ2w(KH3xQ4J^!C9E`aC>WE4OHvY` z8{3MVpluSR4XB(}wrP`C`D25r%O6%%ZH%fgP3<(57SOgr>GsE%HmOt^(zGJBcTRkE z{9}v!KF;0q=lj0D?|Vmo?K^tA_LWHBIsl-bu9bAz>H6w?kH}nqWArRyI~|u!IsNM# z&c~g=acNfT_RnCt`7RkJLvQ%40&9u-RJCcjz;!5>eBVB+tCQy0fmR?Wf&TBN%$)B zC8K#Sj4?{Ke=j`F7K<>suZ1grdHR{LfeeOFT3J(GF zGK$uouYK6}d*7({GjGNltT|gFd9HYd-CuKiTxVQ8&R;q=J3e+yIRf@y*oSQYw2iXw z!8`0T@DNo-fS#$C3+!qIgM*B5w3`jpi9*7R3dUGWU`#Y37{A)WMuv$N$0DaHs;o%M z!$x>3wM00H3mQj6Sx}_t zRBK-NlCF4}IF(m&;zXjvMVZqyO_ob_RpXfFL@bDk(wz>pxIwyY(fBhv;1kB%Ti8Z% zDVs4*1hQP2!j7l~(}75V-M@W_z25o_HuJbbm0(&^O2bIA3-sqsF7uNhEjndM60d2v z#EQa>$cIP5c}d1AL$EPtLtYDBlLf7!7IcS*O(RWtZn+j}i5ApjRV0ZTZW(FJOEMlj zwWI~M^u&}|$rrqaMLD83xWLZEDE#XqxGHCX#Z^_Q3RhKSHLBNJ+}j_PafxQ0T;8c@ zf8nK8J!EnJc(sfxh$X3*w?-8tA*Kf{?pwD?xr(NWmCsc)O>NWbEbhfVw!~gV)~ZBI zQ7~!O0~YtK-ZCyBRWd~(GT)*5E$+KLWn5fUXRN86IXY1^vI zm1qTH-D7bdbd+(azpHSGtO&Z>;$9pmF41_A=uV6K z^DmTfMX7RI<7t=GB;8?gUx=4+d8JBG5MC2y-EMJzCYN$WMXC~C+F2x3w^`h+WQp6I zPcT6is+=px6454EV}1+Uoc--)wx4+_R4X$4uo(UJW4D>Yox3RDd3W2XTAut&3F4|i$1ji|> zp^TNKtRAL*E)=W-8I(@V_7_;$?`?+=)@6DEelU12(`W5u@9lt5rf#R-5BkHilh%Eo zammLvX5-I5i3uF^`GGP%`?R&2{nk#HV(OZ`REfx@^l)M0-m|cV@vo{uKuJ7q-8O2! z0$;1!;r4^W!ln)DN^3 zr!9GdzU+hR@DqDvae`hNfE3#5l--m16dgsAdyLkbusJk45(|Lgbp2dMPM05{BB$_FXD$6$ z2LhA58;ca_>tQF1{lgb_TFEYl}4mMbn5rm_|y^3*F`E>GE z>3mNIayea(3!P%o>JUZFLAx#3u1RS;zC>AGWMSMu delta 2296 zcmai0Yitx%6ux&(x80VVncZ&7(k}ENVkyw!&fIx7rdE)b;xhtjNFmx5Y*~i8Ee&3L`$NH~keL1$Ni@nIABh@%XcYAaB${Y^02*VWje2KyXImf9&F*CP-gCe6 zopZi(?v9+3M&1%1O(%vCLMH!&ufFjm6c4|;>3JDis}4<6#g|R0Ez7GW52YTCMi$Do z($r)t`9hkQQtBI^t^qpm;MUB*P*0|J&(L7+u3f!5tE4#-imAyrAg#pXQ2Tk9T)$IeK{*rQycxNaftyVn7Apv zHZj_kme97<`}c|6-6Cp^cKXZjfvnIrbmz|A-ptUJ?YoAS?%3Sl+oM^^j!eHN&k<2~ zLwjh=QoCV*ZM+6jwb`2+WaQz2Y(u!|e>Mb5F#CNuq>{PH+3~;ZdaaCa|H>Y{7c@4w z9M;t3HzpG(D|`L9&JqH*`ED;X`j_s7uQB^{8LWwC7bPUry*sz5Ada$LGX2+w@v*eYkvS7-nHf@382j^I_Su}^5h z^Zm{~VGJ{Ai;!lg`h}H#`xfDo{yI@~P$fKdkN7>aJ{)Nyw7X71XW>1P{OLK}PN05sO+p?M}o zbAaJmgt`Id*MR961XfD@I;$>z^Ef^O4XaaSdIe4xuUu zJ>D09z4Rb7R&bpi;c^;JW*4`{QP*m3;|%tj=RuOaIRTaI%mH{Y#Is$UC>FISF+;p} zcmzu{qUjx~+EEJBcB!UVj&5j17SEMYr-!|UbV;jh-46IX^-rnVfmDy36Rw&oR-Z;S zD1*c)F&3K_`LUua`ccgVl&PUYloP%=nM*10>EagSZh4&dUxBB_NxG`7XpW&;Wu9l& zK$|j?XTgk6#jtJ1Bzav+pnbVWcK5#|;c{Po8HO=yKMJ!;E)41#j$&IXC3={Olh^Xp z-fM5@G1B}NZj2nqwO8VjiS(=I|$p@ALG1K?~a31;-}iKN)%fsj#cJ2ay8T?gA}ML zae6dOwMk(HZ>~@yekO&r=yUrUEJ~C zN;=HCpDgE)I6U^``p%01g#V*#IsvfTVzT&XZ$A`s@LVdp7sSPIP=Ipk= zNzH%zML3D;iK{z-er;1X9V5)~s_QxRv6rB+KEJ`PMP8TJp1)-pz#l#a7Yk=?SL21Z zO->{{dv@&%wkmB=xeMDRj$#mlXwGQNO3nmqj*8xu-2viRyg+lQtvHTKsTsywLePWZ zLQLHvHhcXUP}vX9fW4r+sap!M4b@b$t{LZ&_4=pT29->+$O$;bqosO4MKvjZhm7y$ zjLY3xgrwA^rlLDUb=16W{7<(YF18wVRmU<%EuLfHJ*c=oQR#gAW9Y=rpg>|Dnf+$?V6q*PDB7>8wyXM0&unSCt$fm9RL6T diff --git a/links/migrations/0016_migrate_existing_screenshots.py b/links/migrations/0016_migrate_existing_screenshots.py new file mode 100644 index 0000000..b0626e9 --- /dev/null +++ b/links/migrations/0016_migrate_existing_screenshots.py @@ -0,0 +1,31 @@ +from django.db import migrations + +def migrate_screenshots(apps, schema_editor): + Page = apps.get_model('links', 'Page') + Screenshot = apps.get_model('links', 'Screenshot') + + for page in Page.objects.all(): + if page.screenshot_path: + Screenshot.objects.create( + page=page, + path=page.screenshot_path, + status='completed' + ) + +def reverse_migrate(apps, schema_editor): + Page = apps.get_model('links', 'Page') + Screenshot = apps.get_model('links', 'Screenshot') + + for screenshot in Screenshot.objects.filter(status='completed'): + screenshot.page.screenshot_path = screenshot.path + screenshot.page.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('links', '0015_create_screenshot_model'), + ] + + operations = [ + migrations.RunPython(migrate_screenshots, reverse_migrate), + ] diff --git a/links/migrations/0017_remove_old_screenshot_fields.py b/links/migrations/0017_remove_old_screenshot_fields.py new file mode 100644 index 0000000..5453962 --- /dev/null +++ b/links/migrations/0017_remove_old_screenshot_fields.py @@ -0,0 +1,14 @@ +from django.db import migrations + +class Migration(migrations.Migration): + + dependencies = [ + ('links', '0016_migrate_existing_screenshots'), + ] + + operations = [ + migrations.RemoveField( + model_name='page', + name='screenshot_path', + ), + ] diff --git a/links/migrations/0018_page_screenshot_path.py b/links/migrations/0018_page_screenshot_path.py new file mode 100644 index 0000000..77ffd2b --- /dev/null +++ b/links/migrations/0018_page_screenshot_path.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.9 on 2024-11-13 11:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('links', '0017_remove_old_screenshot_fields'), + ] + + operations = [ + migrations.AddField( + model_name='page', + name='screenshot_path', + field=models.CharField(blank=True, max_length=255), + ), + ] diff --git a/links/models.py b/links/models.py index db3bbcf..3ea34e2 100644 --- a/links/models.py +++ b/links/models.py @@ -177,10 +177,14 @@ class Page(models.Model): self.retry_count < 3 def get_screenshot_url(self): - if self.screenshot_path: - return f'/media/{self.screenshot_path}' + latest_screenshot = self.screenshots.first() + if latest_screenshot and latest_screenshot.path: + return os.path.join(settings.MEDIA_URL, latest_screenshot.path) return None + def get_latest_screenshot(self): + return self.screenshots.first() + class Screenshot(models.Model): class Status(models.TextChoices): PENDING = 'pending', _('Pending') diff --git a/links/page_views.py b/links/page_views.py index 7785baf..3593547 100644 --- a/links/page_views.py +++ b/links/page_views.py @@ -222,6 +222,40 @@ class PageViewSet(viewsets.ModelViewSet): 'error': str(e) }, status=400) + @action(detail=False, methods=['delete'], url_path='screenshots/(?P[^/.]+)') + def delete_screenshot(self, request, screenshot_id=None): + """Delete a specific screenshot""" + try: + screenshot = Screenshot.objects.get(id=screenshot_id) + + # Optional: Add permission check here + # if screenshot.page.user != request.user: + # return Response(status=status.HTTP_403_FORBIDDEN) + + # Delete the actual file + if screenshot.path: + file_path = os.path.join(settings.MEDIA_ROOT, screenshot.path) + try: + os.remove(file_path) + except OSError: + pass # File doesn't exist or can't be deleted + + # Delete the database record + screenshot.delete() + + return Response(status=status.HTTP_204_NO_CONTENT) + + except Screenshot.DoesNotExist: + return Response( + {"error": "Screenshot not found"}, + status=status.HTTP_404_NOT_FOUND + ) + except Exception as e: + return Response( + {"error": str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + class ScreenshotGalleryView(TemplateView): template_name = 'links/screenshot_gallery.html' diff --git a/links/tasks.py b/links/tasks.py index 4c2ce3d..af7d6c4 100644 --- a/links/tasks.py +++ b/links/tasks.py @@ -213,7 +213,7 @@ def capture_screenshot(self, page_id, screenshot_id): @shared_task(bind=True, max_retries=3) def process_page(self, page_id): """Task to process a page and extract its metadata""" - from .models import Page + from .models import Page, Screenshot try: page = Page.objects.get(id=page_id) @@ -250,12 +250,18 @@ def process_page(self, page_id): if description: page.summary = description[:500] - # Trigger screenshot capture if needed - if not page.screenshot_path: - capture_screenshot.delay(page.id) + # Create a new screenshot record and trigger capture + screenshot = Screenshot.objects.create( + page=page, + status=Screenshot.Status.PENDING + ) + + # Trigger screenshot capture with the new screenshot ID + capture_screenshot.delay(page.id, screenshot.id) page.process_status = Page.ProcessStatus.COMPLETED page.save() + logger.info(f"Successfully processed page {page_id}") except Exception as exc: diff --git a/links/templates/links/page_detail.html b/links/templates/links/page_detail.html index bb247f6..72381ee 100644 --- a/links/templates/links/page_detail.html +++ b/links/templates/links/page_detail.html @@ -5,6 +5,19 @@ {% block content %}
+ + +
@@ -113,9 +126,11 @@

{% trans "Screenshots" %}

-
@@ -167,31 +182,54 @@

{% trans "Previous Screenshots" %}

{% for screenshot in previous_screenshots %} -
- - {% if screenshot.path %} - Screenshot {{ forloop.counter }} - {% else %} - Pending screenshot {{ forloop.counter }} - {% endif %} - -
- - {{ screenshot.get_status_display }} - -
-
- - {{ screenshot.created_at|date:"Y-m-d H:i" }} - +
+ +
+ + + {% if screenshot.path %} + Screenshot {{ forloop.counter }} + {% else %} + Pending screenshot {{ forloop.counter }} + {% endif %} + + + +
+
+ +
+
+ + +
+
+ + {{ screenshot.get_status_display }} + + + + {{ screenshot.created_at|date:"Y-m-d H:i" }} + + +
+
{% endfor %} @@ -255,5 +293,26 @@ function getCookie(name) { } return cookieValue; } + +function deleteScreenshot(screenshotId) { + if (confirm('{% trans "Are you sure you want to delete this screenshot?" %}')) { + fetch(`/api/pages/screenshots/${screenshotId}`, { + method: 'DELETE', + headers: { + 'X-CSRFToken': getCookie('csrftoken') + } + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + location.reload(); + }) + .catch(error => { + console.error('Error:', error); + alert('Failed to delete screenshot'); + }); + } +} {% endblock %} diff --git a/links/templates/links/page_list.html b/links/templates/links/page_list.html index ee1c333..24d03b6 100644 --- a/links/templates/links/page_list.html +++ b/links/templates/links/page_list.html @@ -16,20 +16,22 @@ {% for page in pages %}
  • - +