From cb71ba89118284a684bf25c2432b3a8b70471d5a Mon Sep 17 00:00:00 2001 From: "Chen, Chien-ting" Date: Thu, 24 Nov 2022 00:24:57 +0800 Subject: [PATCH] add upload image's cropping, and updating. create userpage --- khaikang/models.py | 4 +- khaikang/settings.py | 2 +- khaikang/urls.py | 2 + khaikang/views.py | 111 +++++++++++++++++++++++++++++--- media/static/default_avatar.png | Bin 0 -> 34382 bytes media/static/default_avatar.svg | 97 ++++++++++++++++++++++++++++ static/generic.css | 9 +++ static/timeline.js | 15 +++-- static/user_config.js | 21 ++++++ templates/base_generic.html | 7 +- templates/index.html | 3 + templates/signup.html | 4 +- templates/user_config.html | 11 +++- templates/user_timeline.html | 62 ++++++++++++++++++ 14 files changed, 328 insertions(+), 20 deletions(-) create mode 100644 media/static/default_avatar.png create mode 100644 media/static/default_avatar.svg create mode 100644 static/generic.css create mode 100644 static/user_config.js create mode 100644 templates/user_timeline.html diff --git a/khaikang/models.py b/khaikang/models.py index b5c6482..e6208db 100644 --- a/khaikang/models.py +++ b/khaikang/models.py @@ -6,6 +6,8 @@ import hashlib + + class User(AbstractUser): """customized user field.""" username = models.CharField(max_length=30, unique=True) # max to 30 char @@ -19,7 +21,7 @@ class User(AbstractUser): def upload_path(instance, orig_filename): avatar_filename = hashlib.sha256(orig_filename.encode('utf-8')).hexdigest()[:10] - return f"img/profile/user_{instance.id}/{avatar_filename}" + return f"img/profile/user_{instance.id}/{orig_filename}" avatar = models.ImageField(upload_to=upload_path) diff --git a/khaikang/settings.py b/khaikang/settings.py index 91059c6..9314a6c 100644 --- a/khaikang/settings.py +++ b/khaikang/settings.py @@ -119,7 +119,7 @@ USE_TZ = True STATIC_URL = 'static/' STATICFILES_DIRS= [os.path.join(BASE_DIR, 'static')] # test only -# STATIC_ROOT = '/home/kiantin1/public_html/khaikang/static/' deploy only +STATIC_ROOT = '/home/kiantin1/public_html/khaikang/static/' # Default primary key field type # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field diff --git a/khaikang/urls.py b/khaikang/urls.py index c6af8b1..9077ca8 100644 --- a/khaikang/urls.py +++ b/khaikang/urls.py @@ -19,6 +19,7 @@ from django.urls import include from . import views from . import settings from django.conf.urls.static import static +from django.contrib.staticfiles.urls import staticfiles_urlpatterns urlpatterns = [ path('admin/', admin.site.urls), @@ -30,6 +31,7 @@ urlpatterns = [ path('api/get_recent_posts_counter', views.api_get_recent_posts_counter), path('api/get_latest_posts', views.api_get_latest_posts), path('api/get_previous_posts', views.api_get_previous_posts), + path('user/', views.user_timeline), ] if settings.DEBUG: diff --git a/khaikang/views.py b/khaikang/views.py index 2d284de..794be06 100644 --- a/khaikang/views.py +++ b/khaikang/views.py @@ -1,7 +1,7 @@ import json from django.http import HttpResponse from django.template import loader -from .models import User, Post +from .models import User, Post, Following from django.utils import timezone from django.contrib.auth.forms import UserCreationForm from django.shortcuts import redirect @@ -13,7 +13,10 @@ from django.db.models import Q import string import random from django import forms - +from django.utils.safestring import mark_safe +from string import Template +from django.forms import ImageField +import os def api_get_previous_posts(request): if request.method == 'POST': @@ -94,6 +97,7 @@ def signup(request): def save(self, commit=True): user = super(KhaikangUserCreationForm, self).save(commit=False) user.shown_name = self.cleaned_data["username"] + user.avatar = "static/default_avatar.png" if commit: user.save() return user @@ -114,7 +118,6 @@ def signup(request): if form.is_valid(): form.fields['shown_name'] = form.fields['username'] - print(form.fields['shown_name']) form.save() return redirect('/account/login') #redirect to login @@ -126,33 +129,117 @@ def signup(request): template = loader.get_template('signup.html') return HttpResponse(template.render({'form': form, 'honeypot_name': honeypot_name}, request)) + + + def user_config(request): + from PIL import Image + def erase_exif(image_url): + original_image = Image.open(image_url) + + image_data = list(original_image.getdata()) + image_without_exif = Image.new(original_image.mode, original_image.size) + image_without_exif.putdata(image_data) + + image_without_exif.save(image_url) + + """crop image to square""" + def crop_image(image_path): + img = Image.open(image_path) + width, height = img.size + left = 0 + top = 0 + right = img.width + bottom = img.height + if width > height: + left = (width - height) / 2 + right = (width + height) / 2 + elif width < height: + top = (height - width) / 2 + bottom = (width + height) / 2 + else: + pass + + img = img.crop((left, top, right, bottom)) + img.save(image_path) + + + + class UserConfigForm(forms.ModelForm): class Meta: + model = User - fields = ('shown_name', 'avatar', 'desc', 'email') + fields = ('shown_name', 'avatar', 'desc', 'email') - - current_user = User.objects.get(id=request.user.id) + + + if request.user == AnonymousUser(): + return redirect('/account/login') #redirect to login + + if request.method == "POST": + current_user = User.objects.get(id=request.user.id) + old_image_path = os.path.abspath(f"./media/{current_user.avatar.name}") form = UserConfigForm(request.POST,request.FILES, instance=current_user) - - + if form.is_valid(): + print(old_image_path) + + if os.path.exists(old_image_path) and (old_image_path != os.path.abspath("./media/static/default_avatar.png")): + a = os.remove(old_image_path) + print(a) + form.save() + image_path = os.path.abspath(f"./media/{current_user.avatar.name}") + erase_exif(image_path) + crop_image(image_path) + + + # + #current_user_avatar = request.user.avatar + #print(current_user_avatar) + #erase_exif(current_user_avatar) + else: pass + current_user = User.objects.get(id=request.user.id) form = UserConfigForm(initial={'shown_name': request.user.shown_name, 'desc' : request.user.desc, 'url' : request.user.url, 'email' : request.user.email}) template = loader.get_template('user_config.html') - return HttpResponse(template.render({'form': form}, request)) + return HttpResponse(template.render({'form': form, 'avatar': current_user.avatar}, request)) + +def user_timeline(request, username): + + viewed_user = User.objects.get(username=username) + if Following.objects.filter(follower = request.user.id).filter(followee=viewed_user.id) or request.user.id == viewed_user.id: + viewed_timeline_list = Post.objects.filter(poster = viewed_user.id).order_by('-id')[:20] + else: + viewed_timeline_list = Post.objects.filter(poster = viewed_user.id).filter(privilage = 'public').order_by('-id')[:20] + + latest_received_time = timezone.now() + if len(viewed_timeline_list) > 0: + oldest_received_time = viewed_timeline_list[-1].post_time + else: + oldest_received_time = datetime.strptime("1970/01/01 00:00", "%Y/%m/%d %H:%M") + + template = loader.get_template('user_timeline.html') + + context = { + 'username' : username, + 'user_shown_name' : viewed_user.shown_name, + 'latest_received_time' : latest_received_time, + 'oldest_received_time' : oldest_received_time, + 'public_timeline_list': viewed_timeline_list, + } + return HttpResponse(template.render(context, request)) def home(request): @@ -162,7 +249,11 @@ def home(request): public_timeline_list = Post.objects.filter(privilage = 'public').order_by('-id')[:20] latest_received_time = timezone.now() - oldest_received_time = public_timeline_list[19].post_time + + if len(public_timeline_list) > 0: + oldest_received_time = public_timeline_list[-1].post_time + else: + oldest_received_time = datetime.strptime("1970/01/01 00:00", "%Y/%m/%d %H:%M") template = loader.get_template('index.html') diff --git a/media/static/default_avatar.png b/media/static/default_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..25d9f2f144673cd466b03a1f78d4cbf370f73f1b GIT binary patch literal 34382 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

0E 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 literal 0 HcmV?d00001 diff --git a/media/static/default_avatar.svg b/media/static/default_avatar.svg new file mode 100644 index 0000000..3a3b3b4 --- /dev/null +++ b/media/static/default_avatar.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/generic.css b/static/generic.css new file mode 100644 index 0000000..4ea3221 --- /dev/null +++ b/static/generic.css @@ -0,0 +1,9 @@ +div.avatar-preview{ + +} + +#avatar-img{ + width: 200px; + height: 200px; + object-fit: cover; +} \ No newline at end of file diff --git a/static/timeline.js b/static/timeline.js index af197b2..3a2acb4 100644 --- a/static/timeline.js +++ b/static/timeline.js @@ -17,8 +17,13 @@ x => timezoneChangingOne(x) + $name = (x) => document.getElementsByName(x); $ = (x) => document.getElementById(x); + +csrf_token = $("_token").content; + + var httpRequest; $("submit_post").addEventListener('click', make_req); @@ -27,12 +32,14 @@ async function make_req() { post_text = $('post_text').value; post_privilage = $('privil_choosing').value; + + if (post_text != ""){ await fetch('/api/post', { method: 'POST', headers: { -"X-CSRFToken": "{{ csrf_token }}", +"X-CSRFToken": csrf_token, 'Content-Type': 'application/json', }, body: JSON.stringify({ @@ -71,7 +78,7 @@ latest_time_string = $("latest_time").innerHTML; await fetch('/api/get_recent_posts_counter', { method: 'POST', headers: { -"X-CSRFToken": "{{ csrf_token }}", +"X-CSRFToken": csrf_token, 'Content-Type': 'application/json', }, body: JSON.stringify({ @@ -105,7 +112,7 @@ oldest_time_string = $("oldest_time").innerHTML; await fetch('/api/get_previous_posts', { method: 'POST', headers: { -"X-CSRFToken": "{{ csrf_token }}", +"X-CSRFToken": csrf_token, 'Content-Type': 'application/json', }, body: JSON.stringify({ @@ -126,7 +133,7 @@ latest_time_string = $("latest_time").innerHTML; await fetch('/api/get_latest_posts', { method: 'POST', headers: { -"X-CSRFToken": "{{ csrf_token }}", +"X-CSRFToken": csrf_token, 'Content-Type': 'application/json', }, body: JSON.stringify({ diff --git a/static/user_config.js b/static/user_config.js new file mode 100644 index 0000000..3ad95d3 --- /dev/null +++ b/static/user_config.js @@ -0,0 +1,21 @@ +$name = (x) => document.getElementsByName(x); +$ = (x) => document.getElementById(x); + +avatarSelector = $name('avatar')[0]; + +avatarSelector.addEventListener('change', updateAvatarPreview); + + +function updateAvatarPreview(event) { + tmpFilename = avatarSelector.files[0]; + if (tmpFilename) { + + fileSizeMaxBound = 4 * (1024 ** 2); // in MB + if (tmpFilename.size > fileSizeMaxBound){ + alert(`The file size is ${tmpFilename.size / (1024 ** 2)} MB, exceeding ${fileSizeMaxBound / (1024 ** 2)}MB`); + avatarSelector.value = ""; + }else{ + $('avatar-img').src = URL.createObjectURL(tmpFilename); + } + } +} \ No newline at end of file diff --git a/templates/base_generic.html b/templates/base_generic.html index 42fb8c4..3e233fa 100644 --- a/templates/base_generic.html +++ b/templates/base_generic.html @@ -1,8 +1,13 @@ +{% load static %} + {% block meta%} + {% endblock %} - {% block title %}Local Library{% endblock %} + {% block title %}Local Library + + {% endblock %} diff --git a/templates/index.html b/templates/index.html index 0497381..9488faf 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,6 +1,9 @@ {% extends "base_generic.html" %} {% load static %} {% load tz %} +{% block meta %} + +{% endblock %} {% get_current_timezone as TIME_ZONE %} {% block headbar %} diff --git a/templates/signup.html b/templates/signup.html index 7c0caad..9b85edc 100644 --- a/templates/signup.html +++ b/templates/signup.html @@ -5,13 +5,13 @@

{% csrf_token %} {{ form.as_p }} - Leave it blank:
+ Leave it blank:
{% endblock %} \ No newline at end of file diff --git a/templates/user_config.html b/templates/user_config.html index cccd6ab..f7d72d4 100644 --- a/templates/user_config.html +++ b/templates/user_config.html @@ -1,10 +1,19 @@ {% extends "base_generic.html" %} +{% load static %} + {% block content %}
{% csrf_token %} -{{ form.as_p }} +

{{ form.shown_name.label}}: {{ form.shown_name}}

+

Avatar Preview:

+

{{ form.avatar.label}}: {{ form.avatar}}

+

{{ form.desc.label}}: {{ form.desc}}

+

{{ form.email.label}}: {{ form.email}}

+ + {% endblock %} \ No newline at end of file diff --git a/templates/user_timeline.html b/templates/user_timeline.html new file mode 100644 index 0000000..9c0d11c --- /dev/null +++ b/templates/user_timeline.html @@ -0,0 +1,62 @@ +{% extends "base_generic.html" %} +{% load static %} +{% load tz %} +{% block meta %} + +{% endblock %} + +{% get_current_timezone as TIME_ZONE %} +{% block headbar %} +{{ request.user.shown_name }} (My timeline) - Configs - Log out +{% endblock %} + +{% block content %} +
+ {% csrf_token %} +
+ + + +
+ + {% csrf_token %} + + + + + + +
+
{{user_shown_name}}
{{username}}
+
+
+
{{latest_received_time|date:"Y-m-d H:i:s.u"}}+0000
+ + {% for post in public_timeline_list %} + +
{{public_post.poster.shown_name}} + at {{public_post.post_time|date:"Y-m-d H:i:s.u"}}+0000
+ {{public_post.text|linebreaksbr}}
+ ↩️ + - 🔁 + - +
+ {% endfor %} + +
{{oldest_received_time|date:"Y-m-d H:i:s.u"}}+0000
+ +
+ +
 % static 'timeline.js' 要修改
+    
+ +{% endblock %} \ No newline at end of file