diff --git a/chirper/blog/migrations/0003_remove_post_title.py b/chirper/blog/migrations/0003_remove_post_title.py new file mode 100644 index 0000000..a48f622 --- /dev/null +++ b/chirper/blog/migrations/0003_remove_post_title.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.6 on 2020-05-26 03:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0002_auto_20200430_0415'), + ] + + operations = [ + migrations.RemoveField( + model_name='post', + name='title', + ), + ] diff --git a/chirper/blog/models.py b/chirper/blog/models.py index a2f5786..897e04a 100644 --- a/chirper/blog/models.py +++ b/chirper/blog/models.py @@ -1,15 +1,18 @@ from django.db import models from django.utils import timezone from django.contrib.auth.models import User +from django.urls import reverse # Create your models here. class Post(models.Model): - title = models.CharField(max_length=100) content = models.TextField() date_posted = models.DateTimeField(auto_now_add=True) author = models.ForeignKey(User, on_delete=models.CASCADE) likes = models.IntegerField(default=0) def __str__(self): - return self.title + return f"{self.author}'s post on {self.date_posted}" + + def get_absolute_url(self): + return reverse('post-detail', kwargs={'pk': self.pk}) diff --git a/chirper/blog/static/Segoe UI Bold Italic.ttfZone.Identifier b/chirper/blog/static/Segoe UI Bold Italic.ttfZone.Identifier new file mode 100644 index 0000000..37425d8 --- /dev/null +++ b/chirper/blog/static/Segoe UI Bold Italic.ttfZone.Identifier @@ -0,0 +1,3 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=C:\Users\rushi\Downloads\segoe-ui-4-cufonfonts.zip diff --git a/chirper/blog/static/Segoe UI Bold.ttfZone.Identifier b/chirper/blog/static/Segoe UI Bold.ttfZone.Identifier new file mode 100644 index 0000000..37425d8 --- /dev/null +++ b/chirper/blog/static/Segoe UI Bold.ttfZone.Identifier @@ -0,0 +1,3 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=C:\Users\rushi\Downloads\segoe-ui-4-cufonfonts.zip diff --git a/chirper/blog/static/Segoe UI Italic.ttfZone.Identifier b/chirper/blog/static/Segoe UI Italic.ttfZone.Identifier new file mode 100644 index 0000000..37425d8 --- /dev/null +++ b/chirper/blog/static/Segoe UI Italic.ttfZone.Identifier @@ -0,0 +1,3 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=C:\Users\rushi\Downloads\segoe-ui-4-cufonfonts.zip diff --git a/chirper/blog/static/Segoe UI.ttfZone.Identifier b/chirper/blog/static/Segoe UI.ttfZone.Identifier new file mode 100644 index 0000000..37425d8 --- /dev/null +++ b/chirper/blog/static/Segoe UI.ttfZone.Identifier @@ -0,0 +1,3 @@ +[ZoneTransfer] +ZoneId=3 +ReferrerUrl=C:\Users\rushi\Downloads\segoe-ui-4-cufonfonts.zip diff --git a/chirper/blog/static/blog/Segoe UI Bold Italic.ttf b/chirper/blog/static/blog/Segoe UI Bold Italic.ttf new file mode 100644 index 0000000..d134de8 Binary files /dev/null and b/chirper/blog/static/blog/Segoe UI Bold Italic.ttf differ diff --git a/chirper/blog/static/blog/Segoe UI Bold.ttf b/chirper/blog/static/blog/Segoe UI Bold.ttf new file mode 100644 index 0000000..5723e8b Binary files /dev/null and b/chirper/blog/static/blog/Segoe UI Bold.ttf differ diff --git a/chirper/blog/static/blog/Segoe UI Italic.ttf b/chirper/blog/static/blog/Segoe UI Italic.ttf new file mode 100644 index 0000000..07fdf85 Binary files /dev/null and b/chirper/blog/static/blog/Segoe UI Italic.ttf differ diff --git a/chirper/blog/static/blog/Segoe UI.ttf b/chirper/blog/static/blog/Segoe UI.ttf new file mode 100644 index 0000000..46b3b99 Binary files /dev/null and b/chirper/blog/static/blog/Segoe UI.ttf differ diff --git a/chirper/blog/static/blog/styles.css b/chirper/blog/static/blog/styles.css index 1ae2a57..363abcc 100644 --- a/chirper/blog/static/blog/styles.css +++ b/chirper/blog/static/blog/styles.css @@ -3,11 +3,16 @@ src: url(futura.ttf); } +@font-face { + font-family: 'Segoe UI'; + src: url('Segoe UI.ttf'); +} + body { background: #fafafa; color: #333333; margin-top: 5rem; - font-family: Futura; + font-family: 'Segoe UI'; } h1, h2, h3, h4, h5, h6 { @@ -21,7 +26,7 @@ ul { .bg-steel { background: #5f788a; background: linear-gradient(to right, #ec008c, #fc6767); - border-bottom: 3px #111 solid; + /* border-bottom: 3px #111 solid; */ } .site-header .navbar-nav .nav-link { @@ -90,3 +95,8 @@ a.article-title:hover { .account-heading { font-size: 2.5rem; } + +.nounderline { + text-decoration: none !important; + color: black !important; +} diff --git a/chirper/blog/templates/blog/base.html b/chirper/blog/templates/blog/base.html index 9d75617..3067fbb 100644 --- a/chirper/blog/templates/blog/base.html +++ b/chirper/blog/templates/blog/base.html @@ -35,7 +35,13 @@ @@ -57,6 +63,19 @@ {% endif %} {% block content %}{% endblock %} +
+
+

Trending

+ +

+
+
+ diff --git a/chirper/blog/templates/blog/home.html b/chirper/blog/templates/blog/home.html index 4fb5a32..bc19523 100644 --- a/chirper/blog/templates/blog/home.html +++ b/chirper/blog/templates/blog/home.html @@ -2,14 +2,35 @@ {% block content %} {% for post in posts %}
+ Profile Picture
-

{{ post.title }}

-

{{ post.content }}

+

{{ post.content }}

{% endfor %} + {% if is_paginated %} + + {% if page_obj.has_previous %} + First + Previous + {% endif %} + + {% for num in page_obj.paginator.page_range %} + {% if page_obj.number == num %} + {{ num }} + {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %} + {{ num }} + {% endif %} + {% endfor %} + + {% if page_obj.has_next %} + Next + Last + {% endif %} + + {% endif %} {% endblock content %} diff --git a/chirper/blog/templates/blog/post_confirm_delete.html b/chirper/blog/templates/blog/post_confirm_delete.html new file mode 100644 index 0000000..d175393 --- /dev/null +++ b/chirper/blog/templates/blog/post_confirm_delete.html @@ -0,0 +1,27 @@ +{% extends "blog/base.html" %} +{% block content %} +
+
+ {% csrf_token %} +
+ Delete post +

Are you sure you want to delete this post?

+ +
+
+ + No, take me back +
+
+
+ +{% endblock content %} diff --git a/chirper/blog/templates/blog/post_detail.html b/chirper/blog/templates/blog/post_detail.html new file mode 100644 index 0000000..e6de4cc --- /dev/null +++ b/chirper/blog/templates/blog/post_detail.html @@ -0,0 +1,19 @@ +{% extends "blog/base.html" %} +{% block content %} +
+ Profile Picture +
+ +

{{ post.content }}

+
+
+{% endblock content %} diff --git a/chirper/blog/templates/blog/post_form.html b/chirper/blog/templates/blog/post_form.html new file mode 100644 index 0000000..7a07423 --- /dev/null +++ b/chirper/blog/templates/blog/post_form.html @@ -0,0 +1,16 @@ +{% extends "blog/base.html" %} +{% load crispy_forms_tags %} +{% block content %} +
+
+ {% csrf_token %} +
+ {{ form | crispy}} +
+
+ +
+
+
+ +{% endblock content %} diff --git a/chirper/blog/templates/blog/user_posts.html b/chirper/blog/templates/blog/user_posts.html new file mode 100644 index 0000000..bf349cc --- /dev/null +++ b/chirper/blog/templates/blog/user_posts.html @@ -0,0 +1,37 @@ +{% extends "blog/base.html" %} +{% block content %} +

Posts by {{ view.kwargs.username }} ({{ page_obj.paginator.count }})

+ {% for post in posts %} +
+ Profile Picture +
+ +

{{ post.content }}

+
+
+ {% endfor %} + {% if is_paginated %} + + {% if page_obj.has_previous %} + First + Previous + {% endif %} + + {% for num in page_obj.paginator.page_range %} + {% if page_obj.number == num %} + {{ num }} + {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %} + {{ num }} + {% endif %} + {% endfor %} + + {% if page_obj.has_next %} + Next + Last + {% endif %} + + {% endif %} +{% endblock content %} diff --git a/chirper/blog/urls.py b/chirper/blog/urls.py index 2f2d169..205972c 100644 --- a/chirper/blog/urls.py +++ b/chirper/blog/urls.py @@ -1,7 +1,23 @@ from django.urls import path +from .views import ( + PostListView, + PostDetailView, + PostCreateView, + PostUpdateView, + PostDeleteView, + UserPostListView, +) from . import views + urlpatterns = [ - path('', views.home, name='blog-home'), + path('', PostListView.as_view(), name='blog-home'), + + path('user/', UserPostListView.as_view(), name='user-posts'), + path('post//', PostDetailView.as_view(), name='post-detail'), + path('post/new/', PostCreateView.as_view(), name='post-create'), + path('post//update/', PostUpdateView.as_view(), name='post-update'), + path('post//delete/', PostDeleteView.as_view(), name='post-delete'), + path('about/', views.about, name='blog-about'), ] diff --git a/chirper/blog/views.py b/chirper/blog/views.py index 1067259..82f6ecc 100644 --- a/chirper/blog/views.py +++ b/chirper/blog/views.py @@ -1,4 +1,13 @@ -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 +from django.contrib.auth.models import User +from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.views.generic import ( + ListView, + DetailView, + CreateView, + UpdateView, + DeleteView, +) from .models import Post def home (request): @@ -7,5 +16,53 @@ def home (request): } return render(request, 'blog/home.html', context) +class PostListView(ListView): + model = Post + template_name = "blog/home.html" + context_object_name='posts' + ordering = ['-date_posted'] + paginate_by = 8 + +class UserPostListView(ListView): + model = Post + template_name = "blog/user_posts.html" + context_object_name='posts' + paginate_by = 8 + + def get_queryset(self): + user = get_object_or_404(User, username=self.kwargs.get('username')) + return Post.objects.filter(author=user).order_by('-date_posted') + +class PostDetailView(DetailView): + model = Post + +class PostCreateView(LoginRequiredMixin, CreateView): + model = Post + fields=['content'] + + def form_valid(self, form): + form.instance.author = self.request.user + return super().form_valid(form) + +class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): + model = Post + fields=['content'] + + def form_valid(self, form): + form.instance.author = self.request.user + return super().form_valid(form) + + def test_func(self): + post = self.get_object() + return self.request.user == post.author + +class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): + model = Post + success_url='/' + + def test_func(self): + post = self.get_object() + return self.request.user == post.author + def about (request): return render(request, 'blog/about.html', {'title': 'About'}) diff --git a/chirper/chirper/settings.py b/chirper/chirper/settings.py index 213e042..c9efa9f 100644 --- a/chirper/chirper/settings.py +++ b/chirper/chirper/settings.py @@ -46,6 +46,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'crispy_forms', 'oauth2_provider', 'corsheaders', ] @@ -131,3 +132,11 @@ USE_TZ = True # https://docs.djangoproject.com/en/3.0/howto/static-files/ STATIC_URL = '/static/' + +CRISPY_TEMPLATE_PACK = 'bootstrap4' + +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') +MEDIA_URL = '/media/' + +LOGIN_REDIRECT_URL = 'blog-home' +LOGIN_URL = 'login' diff --git a/chirper/chirper/urls.py b/chirper/chirper/urls.py index 1d5cec3..1166080 100644 --- a/chirper/chirper/urls.py +++ b/chirper/chirper/urls.py @@ -13,6 +13,8 @@ Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ +from django.conf import settings +from django.conf.urls.static import static from django.contrib import admin from django.urls import path, include from users import views as user_views @@ -20,6 +22,12 @@ from users import views as user_views urlpatterns = [ path('', include('blog.urls')), path('admin/', admin.site.urls), - path('login/', include('users.urls')), + path('login/', user_views.login, name='login'), + path('logout/', user_views.logout, name='logout'), + path('callback/', user_views.callback), + path('profile/', user_views.profile, name='profile'), path("o/", include('oauth2_provider.urls', namespace='oauth2_provider')), ] + +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/chirper/manage.py b/chirper/manage.py old mode 100755 new mode 100644 diff --git a/chirper/media/default.jpg b/chirper/media/default.jpg new file mode 100644 index 0000000..ee224a6 Binary files /dev/null and b/chirper/media/default.jpg differ diff --git a/chirper/media/default2.jpg b/chirper/media/default2.jpg new file mode 100644 index 0000000..01968b0 Binary files /dev/null and b/chirper/media/default2.jpg differ diff --git a/chirper/media/profile_pics/pfp.jpg b/chirper/media/profile_pics/pfp.jpg new file mode 100644 index 0000000..160d279 Binary files /dev/null and b/chirper/media/profile_pics/pfp.jpg differ diff --git a/chirper/media/profile_pics/pfp_42i5z4n.jpg b/chirper/media/profile_pics/pfp_42i5z4n.jpg new file mode 100644 index 0000000..160d279 Binary files /dev/null and b/chirper/media/profile_pics/pfp_42i5z4n.jpg differ diff --git a/chirper/media/profile_pics/pfp_cnFx55X.jpg b/chirper/media/profile_pics/pfp_cnFx55X.jpg new file mode 100644 index 0000000..8b3fde7 Binary files /dev/null and b/chirper/media/profile_pics/pfp_cnFx55X.jpg differ diff --git a/chirper/media/profile_pics/pfp_wMzpNdz.jpg b/chirper/media/profile_pics/pfp_wMzpNdz.jpg new file mode 100644 index 0000000..160d279 Binary files /dev/null and b/chirper/media/profile_pics/pfp_wMzpNdz.jpg differ diff --git a/chirper/users/admin.py b/chirper/users/admin.py index 8c38f3f..123dd95 100644 --- a/chirper/users/admin.py +++ b/chirper/users/admin.py @@ -1,3 +1,5 @@ from django.contrib import admin +from .models import Profile # Register your models here. +admin.site.register(Profile) diff --git a/chirper/users/apps.py b/chirper/users/apps.py index 4ce1fab..b8d67f1 100644 --- a/chirper/users/apps.py +++ b/chirper/users/apps.py @@ -3,3 +3,6 @@ from django.apps import AppConfig class UsersConfig(AppConfig): name = 'users' + + def ready(self): + import users.signals diff --git a/chirper/users/forms.py b/chirper/users/forms.py new file mode 100644 index 0000000..bba9641 --- /dev/null +++ b/chirper/users/forms.py @@ -0,0 +1,22 @@ +from django import forms +from django.contrib.auth.models import User +from .models import Profile + +class UserUpdateForm(forms.ModelForm): + + email = forms.EmailField() + + def __init__(self, *args, **kwargs): + super(UserUpdateForm, self).__init__(*args, **kwargs) + instance = getattr(self, 'instance', None) + if instance and instance.pk: + self.fields['username'].widget.attrs['readonly'] = True + + class Meta: + model = User + fields = ['username', 'email'] + +class ProfileUpdateForm(forms.ModelForm): + class Meta: + model = Profile + fields = ['profile_pic'] diff --git a/chirper/users/migrations/0001_initial.py b/chirper/users/migrations/0001_initial.py new file mode 100644 index 0000000..7cd90b8 --- /dev/null +++ b/chirper/users/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 3.0.6 on 2020-05-26 03:31 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('profile_pic', models.ImageField(default='default.jpg', upload_to='profile.pics')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/chirper/users/migrations/0002_auto_20200526_0344.py b/chirper/users/migrations/0002_auto_20200526_0344.py new file mode 100644 index 0000000..ab57398 --- /dev/null +++ b/chirper/users/migrations/0002_auto_20200526_0344.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.6 on 2020-05-26 03:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='profile_pic', + field=models.ImageField(default='default.jpg', upload_to='profile_pics'), + ), + ] diff --git a/chirper/users/models.py b/chirper/users/models.py index 71a8362..0fe5e30 100644 --- a/chirper/users/models.py +++ b/chirper/users/models.py @@ -1,3 +1,22 @@ from django.db import models +from django.contrib.auth.models import User +from PIL import Image # Create your models here. + +class Profile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + profile_pic = models.ImageField(default='default.jpg', upload_to='profile_pics') + + def __str__ (self): + return f"{self.user.username}'s Profile" + + def save(self): + super().save() + + img = Image.open(self.profile_pic.path) + + if img.height > 300 or img.width > 300: + size = (300, 300) + img.thumbnail(size) + img.save(self.profile_pic.path) diff --git a/chirper/users/signals.py b/chirper/users/signals.py new file mode 100644 index 0000000..5be6310 --- /dev/null +++ b/chirper/users/signals.py @@ -0,0 +1,13 @@ +from django.db.models.signals import post_save +from django.contrib.auth.models import User +from django.dispatch import receiver +from .models import Profile + +@receiver(post_save, sender=User) +def create_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.create(user=instance) + +@receiver(post_save, sender=User) +def save_profile(sender, instance, **kwargs): + instance.profile.save() diff --git a/chirper/users/templates/users/login.html b/chirper/users/templates/users/login.html index 50eecdd..04e3041 100644 --- a/chirper/users/templates/users/login.html +++ b/chirper/users/templates/users/login.html @@ -1,6 +1,9 @@ {% extends "blog/base.html" %} {% block content %} - + {% endblock content %} diff --git a/chirper/users/templates/users/logout.html b/chirper/users/templates/users/logout.html new file mode 100644 index 0000000..8a4e050 --- /dev/null +++ b/chirper/users/templates/users/logout.html @@ -0,0 +1,9 @@ +{% extends "blog/base.html" %} +{% block content %} +

You have been logged out

+
+ + Log In Again + +
+{% endblock content %} diff --git a/chirper/users/templates/users/profile.html b/chirper/users/templates/users/profile.html new file mode 100644 index 0000000..3c529bc --- /dev/null +++ b/chirper/users/templates/users/profile.html @@ -0,0 +1,25 @@ +{% extends "blog/base.html" %} +{% load crispy_forms_tags %} +{% block content %} +
+
+ +
+ +

{{ user.email }}

+
+
+
+ {% csrf_token %} +
+ Your profile + {{ userForm|crispy}} + {{ profileForm|crispy }} +
+
+ +
+
+
+ +{% endblock content %} diff --git a/chirper/users/urls.py b/chirper/users/urls.py deleted file mode 100644 index 9082c24..0000000 --- a/chirper/users/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.urls import path -from . import views - -urlpatterns = [ - path('', views.login, name='login'), - path('callback/', views.callback) -] diff --git a/chirper/users/views.py b/chirper/users/views.py index 8929f7e..2089409 100644 --- a/chirper/users/views.py +++ b/chirper/users/views.py @@ -1,19 +1,28 @@ -from django.shortcuts import render, redirect -from django.contrib.auth.models import User -from django.contrib.auth import authenticate -from django.contrib.auth import login as auth_login -from django.contrib import messages -from requests_oauthlib import OAuth2Session import json import requests +from django.shortcuts import render, redirect + +from django.contrib.auth import authenticate +from django.contrib.auth import login as auth_login +from django.contrib.auth import logout as auth_logout +from django.contrib.auth.models import User +from django.contrib.auth.decorators import login_required + +from requests_oauthlib import OAuth2Session + +from django.contrib import messages + +from .forms import UserUpdateForm, ProfileUpdateForm + + # Create your views here. client_id = r'6p7HJlFCD8cnBNBEdgMsdULS5ph0jserw1xvWfxX' client_secret = r'E1e79KebxzAp0LBEtxcUg32b0qFP9Ap9Dxqkac6Qhci5AwXFhSfrbe7MtmGJUh6DDgxivJpGgFYNQgusfvoSraDAnsq3NnEET5DmxgfBBvvuYc2bwDq6KpeKIDQqFtwz' -redirect_uri = 'http://localhost:8000/login/callback/' +redirect_uri = 'http://localhost:8000/callback/' token_url = 'https://ion.tjhsst.edu/oauth/authorize/' -scope=["read","write"] +scope=["read"] authorized_users=["2023rumareti", "Your ION_USERNAME"] # Hey that's me @@ -59,11 +68,40 @@ def callback (request): user = User.objects.create_user(username=username, email=email, password=username, first_name=first_name, last_name=last_name) user.save() auth_login(request, user, backend='django.contrib.auth.backends.ModelBackend') - messages.success(request, f"Welcome to Chirper, {first_name}, we hope you like your stay!") - return redirect('/') + messages.success(request, f"Welcome to Chirper, {first_name}, we hope you enjoy your stay!") + return redirect('profile') else: messages.error(request, "Sorry, you're not an authorized Ion user!", extra_tags='danger') - return redirect('/') + return redirect('blog-home') messages.warning(request, "Invalid Callback Response") - return redirect('/') + return redirect('blog-home') + +@login_required +def logout(request): + auth_logout(request) + return render(request, 'users/logout.html') + +@login_required +def profile(request): + if request.method == "POST": + userForm = UserUpdateForm(request.POST, instance=request.user) + profileForm = ProfileUpdateForm(request.POST, + request.FILES, + instance=request.user.profile) + + if userForm.is_valid() and profileForm.is_valid(): + userForm.save() + profileForm.save() + messages.success(request, "Your account has been updated!") + return redirect('profile') + else: + userForm = UserUpdateForm(instance=request.user) + profileForm = ProfileUpdateForm(instance=request.user.profile) + + context = { + 'userForm': userForm, + 'profileForm': profileForm + } + + return render(request, 'users/profile.html', context)