diff --git a/.gitignore b/.gitignore index 6f8f59b..e271278 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +# created by virtualenv automatically *.pyc *.db -*.sqlite3 +*.sqlite3 \ No newline at end of file diff --git a/blogging/admin.py b/blogging/admin.py index 1175916..5a6258a 100644 --- a/blogging/admin.py +++ b/blogging/admin.py @@ -1,6 +1,20 @@ from django.contrib import admin + from blogging.models import Post, Category +class CategoryAdmin(admin.ModelAdmin): + list_display = ('name', 'description') + + exclude = ('posts',) + +class CategoryInLine(admin.TabularInline): + model = Category.posts.through + +class PostAdmin(admin.ModelAdmin): + inlines = [CategoryInLine,] + + list_display = ('title', 'created_date',) + -admin.site.register(Post) -admin.site.register(Category) +admin.site.register(Post, PostAdmin) +admin.site.register(Category, CategoryAdmin) \ No newline at end of file diff --git a/blogging/feeds.py b/blogging/feeds.py new file mode 100644 index 0000000..d88b5b8 --- /dev/null +++ b/blogging/feeds.py @@ -0,0 +1,20 @@ +from django.contrib.syndication.views import Feed +from django.urls import reverse +from blogging.models import Post + +class LatestEntriesFeed(Feed): + title = 'Most Recent Posts' + link = '/sitenews/' + description = '5 Most Recent Posts to Site' + + def items(self): + return Post.objects.order_by('-published_date')[:5] + + def item_title(self, item): + return item.title + + def item_author(self, item): + return item.author + + def item_link(self, item): + return reverse('post', args=[item.pk]) diff --git a/blogging/migrations/0001_initial.py b/blogging/migrations/0001_initial.py index 5d406bf..90f963a 100644 --- a/blogging/migrations/0001_initial.py +++ b/blogging/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.1 on 2019-10-29 01:39 +# Generated by Django 2.1.1 on 2021-06-26 19:07 from django.conf import settings from django.db import migrations, models diff --git a/blogging/migrations/0002_category.py b/blogging/migrations/0002_category.py index 0ccbe19..435d3ab 100644 --- a/blogging/migrations/0002_category.py +++ b/blogging/migrations/0002_category.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.1 on 2019-11-05 03:35 +# Generated by Django 2.1.1 on 2021-07-01 03:23 from django.db import migrations, models @@ -16,7 +16,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=128)), ('description', models.TextField(blank=True)), - ('posts', models.ManyToManyField(blank=True, related_name='categories', to='blogging.Post')), + ('posts', models.ManyToManyField(blank=True, related_name='Categories', to='blogging.Post')), ], ), ] diff --git a/blogging/models.py b/blogging/models.py index 10d6cc3..4d9a2ce 100644 --- a/blogging/models.py +++ b/blogging/models.py @@ -1,24 +1,35 @@ +from typing import Set from django.db import models from django.contrib.auth.models import User class Post(models.Model): + #title - CharField max_length = 60 title = models.CharField(max_length=128) + #text - TextField text = models.TextField(blank=True) + #author - CharField max_length = 30 author = models.ForeignKey(User, on_delete=models.CASCADE) + #created_date - DateField auto_now_add=False created_date = models.DateTimeField(auto_now_add=True) + #modified_date - DateField auto_now=True modified_date = models.DateTimeField(auto_now=True) + #published_date - - DateField auto_now_add=True published_date = models.DateTimeField(blank=True, null=True) def __str__(self): return self.title + class Category(models.Model): + #name name = models.CharField(max_length=128) + #description description = models.TextField(blank=True) - posts = models.ManyToManyField(Post, blank=True, related_name='categories') - - class Meta: - verbose_name_plural = 'Categories' - + #posts + posts = models.ManyToManyField(Post, blank=True, related_name='Categories') + def __str__(self): return self.name + + class Meta: + verbose_name_plural = 'Categories' \ No newline at end of file diff --git a/blogging/serializers.py b/blogging/serializers.py new file mode 100644 index 0000000..ab235d7 --- /dev/null +++ b/blogging/serializers.py @@ -0,0 +1,27 @@ +from django.contrib.auth.models import User, Group +from rest_framework import serializers +from blogging.models import Post, Category + +class UserSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = User + fields = ['url', 'username', 'email', 'groups'] + + +class GroupSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Group + fields = ['url', 'name'] + +class PostSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Post + fields = [ + 'url', 'title', 'text', 'author', 'created_date', + 'modified_date','published_date', + ] + +class CategorySerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Category + fields = ['url', 'name', 'description', 'posts'] diff --git a/blogging/templates/blogging/detail.html b/blogging/templates/blogging/detail.html index ea5b9c8..cfa7b96 100644 --- a/blogging/templates/blogging/detail.html +++ b/blogging/templates/blogging/detail.html @@ -14,4 +14,4 @@

{{ post }}

  • {{ category }}
  • {% endfor %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/blogging/templates/blogging/list.html b/blogging/templates/blogging/list.html index d8aa919..1cd6a3b 100644 --- a/blogging/templates/blogging/list.html +++ b/blogging/templates/blogging/list.html @@ -4,7 +4,7 @@

    Recent Posts

    {% for post in posts %}

    - {{ post }} + {{ post }}

    Posted by {{ post.author.username }} — {{ post.published_date }} @@ -19,4 +19,4 @@

    {% endfor %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/blogging/tests.py b/blogging/tests.py index 4250226..7fee97d 100644 --- a/blogging/tests.py +++ b/blogging/tests.py @@ -1,15 +1,12 @@ import datetime - from django.test import TestCase from django.contrib.auth.models import User from django.utils.timezone import utc - from blogging.models import Post from blogging.models import Category - class PostTestCase(TestCase): - fixtures = ['blogging_test_fixture.json', ] + fixtures = ['blogging_test_fixture.json',] def setUp(self): self.user = User.objects.get(pk=1) @@ -20,16 +17,13 @@ def test_string_representation(self): actual = str(p1) self.assertEqual(expected, actual) - class CategoryTestCase(TestCase): - def test_string_representation(self): expected = "A Category" c1 = Category(name=expected) actual = str(c1) self.assertEqual(expected, actual) - class FrontEndTestCase(TestCase): """test views provided in the front-end""" fixtures = ['blogging_test_fixture.json', ] @@ -69,4 +63,4 @@ def test_details_only_published(self): self.assertEqual(resp.status_code, 200) self.assertContains(resp, title) else: - self.assertEqual(resp.status_code, 404) + self.assertEqual(resp.status_code, 404) \ No newline at end of file diff --git a/blogging/urls.py b/blogging/urls.py index 172a39c..95a2eb7 100644 --- a/blogging/urls.py +++ b/blogging/urls.py @@ -1,7 +1,9 @@ from django.urls import path -from blogging.views import list_view, detail_view +from blogging.views import stub_view +from blogging.views import list_view +from blogging.views import detail_view urlpatterns = [ path('', list_view, name="blog_index"), path('posts//', detail_view, name="blog_detail"), -] +] \ No newline at end of file diff --git a/blogging/views.py b/blogging/views.py index b4bab4f..e405ca0 100644 --- a/blogging/views.py +++ b/blogging/views.py @@ -1,9 +1,30 @@ from django.shortcuts import render from django.http import HttpResponse, HttpResponseRedirect, Http404 from django.template import loader +from django.contrib.auth.models import User, Group +from blogging import serializers +from rest_framework import viewsets +from rest_framework import permissions +from blogging.models import Category, Post +from blogging.serializers import CategorySerializer, PostSerializer, UserSerializer, GroupSerializer +from django.contrib.syndication.views import Feed +from django.urls import reverse -from blogging.models import Post +def stub_view(request, *args, **kwargs): + body = "Stub View\n\n" + if args: + body += "Args:\n" + body += "\n".join(["\t%s" % a for a in args]) + if kwargs: + body += "Kwargs:\n" + body += "\n".join(["\t%s: %s" % i for i in kwargs.items()]) + return HttpResponse(body, content_type="text/plain") +def list_view(request): + published = Post.objects.exclude(published_date__exact=None) + posts = published.order_by('-published_date') + context = {'posts': posts} + return render(request, 'blogging/list.html', context) def detail_view(request, post_id): published = Post.objects.exclude(published_date__exact=None) @@ -14,9 +35,55 @@ def detail_view(request, post_id): context = {'post': post} return render(request, 'blogging/detail.html', context) +class UserViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows users to be viewed or edited. + """ + queryset = User.objects.all().order_by('-date_joined') + serializer_class = UserSerializer + permission_classes = [permissions.IsAuthenticated] -def list_view(request): - published = Post.objects.exclude(published_date__exact=None) - posts = published.order_by('-published_date') - context = {'posts': posts} - return render(request, 'blogging/list.html', context) + +class GroupViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows groups to be viewed or edited. + """ + queryset = Group.objects.all() + serializer_class = GroupSerializer + permission_classes = [permissions.IsAuthenticated] + +class PostViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows Post to be viewed or edited. + """ + queryset = Post.objects.all() + serializer_class = PostSerializer + permission_classes = [permissions.IsAuthenticated] + +class CategoryViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows Categories to be viewed or edited. + """ + queryset = Category.objects.all() + serializer_class = CategorySerializer + permission_classes = [permissions.IsAuthenticated] + +class LatestEntriesFeed(Feed): + title = 'Most Recent Posts' + link = '/sitenews/' + description = '5 Most Recent Posts to Site' + + def items(self): + return Post.objects.order_by('-published_date')[:5] + + def item_title(self, item): + return item.title + + def item_author(self, item): + return item.author + + def item_id(self, item): + return item.pk + + def item_link(self, item): + return reverse('latest-feed', kwargs= {"id" : self.item_id}) \ No newline at end of file diff --git a/mysite/production.py b/mysite/production.py new file mode 100644 index 0000000..ef4250c --- /dev/null +++ b/mysite/production.py @@ -0,0 +1,16 @@ +import os + +import dj_database_url + +from .settings import * + + +DATABASES = { + 'default': dj_database_url.config(default='sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3')) + } + +DEBUG = False +TEMPLATE_DEBUG = False +ALLOWED_HOSTS = [os.environ.get('ALLOWED_HOSTS'), 'localhost'] +STATIC_ROOT = os.path.join(BASE_DIR, 'static') +SECRET_KEY = os.environ.get('SECRET_KEY') \ No newline at end of file diff --git a/mysite/settings.py b/mysite/settings.py index 14e4a11..3ec4c8c 100644 --- a/mysite/settings.py +++ b/mysite/settings.py @@ -37,8 +37,14 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.sites', + 'allauth', + 'allauth.account', + 'allauth.socialaccount', + 'allauth.socialaccount.providers.facebook', 'polling', 'blogging', + 'rest_framework', ] MIDDLEWARE = [ @@ -123,3 +129,15 @@ LOGIN_URL = '/login/' LOGIN_REDIRECT_URL = '/' + +REST_FRAMEWORK = { + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 10 +} + +AUTHENTICATION_BACKENDS = [ + 'django.contrib.auth.backends.ModelBackend', + 'allauth.account.auth_backends.AuthenticationBackend', +] + +SITE_ID = 1 diff --git a/mysite/templates/base.html b/mysite/templates/base.html index 4bb0230..8e6acaf 100644 --- a/mysite/templates/base.html +++ b/mysite/templates/base.html @@ -1,4 +1,4 @@ -{% load staticfiles %} +{% load static %} @@ -6,16 +6,16 @@ -
    {% block content %} @@ -24,4 +24,4 @@
    - + \ No newline at end of file diff --git a/mysite/templates/login.html b/mysite/templates/login.html index 1566d0f..e1a56ee 100644 --- a/mysite/templates/login.html +++ b/mysite/templates/login.html @@ -6,4 +6,4 @@

    My Blog Login

    {{ form.as_p }}

    -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/mysite/urls.py b/mysite/urls.py index 446cd8f..b62c63d 100644 --- a/mysite/urls.py +++ b/mysite/urls.py @@ -16,12 +16,23 @@ from django.contrib import admin from django.urls import path, include from django.contrib.auth.views import LoginView, LogoutView +from rest_framework import routers +from blogging import views +router = routers.DefaultRouter() +router.register(r'users', views.UserViewSet) +router.register(r'groups', views.GroupViewSet) +router.register(r'posts', views.PostViewSet) +router.register(r'categories', views.CategoryViewSet) urlpatterns = [ path('', include('blogging.urls')), + path('', include(router.urls)), path('polling/', include('polling.urls')), path('admin/', admin.site.urls), path('login/', LoginView.as_view(template_name='login.html'), name="login"), path('logout/', LogoutView.as_view(next_page='/'), name="logout"), + path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), + path('latest/feed/', views.LatestEntriesFeed(), name= 'latest-feed'), + path('accounts/', include('allauth.urls')), ] diff --git a/polling/migrations/0001_initial.py b/polling/migrations/0001_initial.py index 2be60e7..aed7133 100644 --- a/polling/migrations/0001_initial.py +++ b/polling/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.1 on 2019-10-29 01:24 +# Generated by Django 2.1.1 on 2021-06-22 12:47 from django.db import migrations, models diff --git a/polling/models.py b/polling/models.py index 6a940d2..d611cbc 100644 --- a/polling/models.py +++ b/polling/models.py @@ -6,4 +6,4 @@ class Poll(models.Model): score = models.IntegerField(default=0) def __str__(self): - return self.title + return self.title \ No newline at end of file diff --git a/polling/templates/polling/detail.html b/polling/templates/polling/detail.html index bb18db0..24ad0dd 100644 --- a/polling/templates/polling/detail.html +++ b/polling/templates/polling/detail.html @@ -1,5 +1,3 @@ -{# polling/templates/polling/detail.html #} - {% extends "base.html" %} {% block content %}

    {{ poll.title }}

    @@ -16,4 +14,4 @@

    {{ poll.title }}

    -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/polling/templates/polling/list.html b/polling/templates/polling/list.html index 9cf4282..fc90c1a 100644 --- a/polling/templates/polling/list.html +++ b/polling/templates/polling/list.html @@ -8,4 +8,4 @@

    {% endfor %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/polling/urls.py b/polling/urls.py index 9c4e2fd..e51198d 100644 --- a/polling/urls.py +++ b/polling/urls.py @@ -4,4 +4,4 @@ urlpatterns = [ path('', list_view, name="poll_index"), path('polls//', detail_view, name="poll_detail"), -] +] \ No newline at end of file diff --git a/polling/views.py b/polling/views.py index 6339808..4db14de 100644 --- a/polling/views.py +++ b/polling/views.py @@ -20,4 +20,4 @@ def detail_view(request, poll_id): poll.save() context = {'poll': poll} - return render(request, 'polling/detail.html', context) + return render(request, 'polling/detail.html', context) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 81580ab..312aa54 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,22 @@ -Django==2.1.1 -pytz==2019.3 +asgiref==3.4.1 +certifi==2021.5.30 +cffi==1.14.6 +chardet==4.0.0 +cryptography==3.4.7 +defusedxml==0.7.1 +dj-database-url==0.5.0 +Django==3.2.5 +django-allauth==0.45.0 +djangorestframework==3.12.4 +idna==2.10 +oauthlib==3.1.1 +psycopg2==2.9.1 +pycparser==2.20 +PyJWT==2.1.0 +python3-openid==3.2.0 +pytz==2021.1 +requests==2.25.1 +requests-oauthlib==1.3.0 +sqlparse==0.4.1 +typing-extensions==3.10.0.0 +urllib3==1.26.6