├── .gitattributes ├── django ├── .coverage ├── blog │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── admin.cpython-38.pyc │ │ ├── models.cpython-38.pyc │ │ ├── tests.cpython-38.pyc │ │ └── urls.cpython-38.pyc │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20200914_1537.py │ │ ├── 0003_post_image.py │ │ ├── __init__.py │ │ └── __pycache__ │ │ │ ├── 0001_initial.cpython-38.pyc │ │ │ ├── 0002_auto_20200914_1537.cpython-38.pyc │ │ │ ├── 0003_post_image.cpython-38.pyc │ │ │ └── __init__.cpython-38.pyc │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── blog_api │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── admin.cpython-38.pyc │ │ ├── models.cpython-38.pyc │ │ ├── serializers.cpython-38.pyc │ │ ├── tests.cpython-38.pyc │ │ ├── urls.cpython-38.pyc │ │ └── views.cpython-38.pyc │ ├── apps.py │ ├── migrations │ │ ├── __init__.py │ │ └── __pycache__ │ │ │ └── __init__.cpython-38.pyc │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── commands.txt ├── core │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-38.pyc │ │ ├── settings.cpython-38.pyc │ │ ├── urls.cpython-38.pyc │ │ └── wsgi.cpython-38.pyc │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── db.sqlite3 ├── htmlcov │ ├── blog___init___py.html │ ├── blog_admin_py.html │ ├── blog_api___init___py.html │ ├── blog_api_admin_py.html │ ├── blog_api_migrations___init___py.html │ ├── blog_api_models_py.html │ ├── blog_api_serializers_py.html │ ├── blog_api_tests_py.html │ ├── blog_api_urls_py.html │ ├── blog_api_views_py.html │ ├── blog_migrations_0001_initial_py.html │ ├── blog_migrations___init___py.html │ ├── blog_models_py.html │ ├── blog_tests_py.html │ ├── blog_urls_py.html │ ├── core___init___py.html │ ├── core_settings_py.html │ ├── core_urls_py.html │ ├── coverage_html.js │ ├── index.html │ ├── jquery.ba-throttle-debounce.min.js │ ├── jquery.hotkeys.js │ ├── jquery.isonscreen.js │ ├── jquery.min.js │ ├── jquery.tablesorter.min.js │ ├── keybd_closed.png │ ├── keybd_open.png │ ├── manage_py.html │ ├── status.json │ └── style.css ├── manage.py ├── media │ └── posts │ │ ├── asd.png │ │ ├── asd_1ibJffY.png │ │ ├── asd_Cu4QEph.png │ │ ├── asd_fE8DF1X.png │ │ ├── asd_lRgFpPm.png │ │ ├── asd_lxjs3Le.png │ │ ├── asd_oruSJ12.png │ │ └── asd_xUsAVvC.png ├── requirements.txt ├── templates │ └── blog │ │ └── index.html └── users │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-38.pyc │ ├── admin.cpython-38.pyc │ ├── models.cpython-38.pyc │ ├── serializers.cpython-38.pyc │ ├── urls.cpython-38.pyc │ └── views.cpython-38.pyc │ ├── admin.py │ ├── apps.py │ ├── migrations │ ├── 0001_initial.py │ ├── __init__.py │ └── __pycache__ │ │ ├── 0001_initial.cpython-38.pyc │ │ └── __init__.cpython-38.pyc │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py └── react └── blogapi ├── .gitignore ├── README.md ├── commands.txt ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── Admin.js ├── App.css ├── App.js ├── App.test.js ├── axios.js ├── components ├── admin │ ├── create.js │ ├── delete.js │ ├── edit.js │ └── posts.js ├── auth │ ├── login.js │ ├── logout.js │ └── register.js ├── footer.js ├── header.js └── posts │ ├── postLoading.js │ ├── posts.js │ ├── search.js │ └── single.js ├── index.css ├── index.js ├── serviceWorker.js └── setupTests.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /django/.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/.coverage -------------------------------------------------------------------------------- /django/blog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/__init__.py -------------------------------------------------------------------------------- /django/blog/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog/__pycache__/admin.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/__pycache__/admin.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog/__pycache__/models.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/__pycache__/models.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog/__pycache__/tests.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/__pycache__/tests.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog/__pycache__/urls.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/__pycache__/urls.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from . import models 3 | 4 | 5 | @admin.register(models.Post) 6 | class AuthorAdmin(admin.ModelAdmin): 7 | list_display = ('title', 'id', 'status', 'slug', 'author') 8 | prepopulated_fields = {'slug': ('title',), } 9 | 10 | 11 | admin.site.register(models.Category) -------------------------------------------------------------------------------- /django/blog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogConfig(AppConfig): 5 | name = 'blog' 6 | -------------------------------------------------------------------------------- /django/blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-14 14:37 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Category', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=100)), 20 | ], 21 | ), 22 | migrations.CreateModel( 23 | name='Post', 24 | fields=[ 25 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('title', models.CharField(max_length=250)), 27 | ('excerpt', models.TextField(null=True)), 28 | ('content', models.TextField()), 29 | ('slug', models.SlugField(max_length=250, unique_for_date='published')), 30 | ('published', models.DateTimeField(default=django.utils.timezone.now)), 31 | ('status', models.CharField(choices=[('draft', 'Draft'), ('published', 'Published')], default='published', max_length=10)), 32 | ], 33 | options={ 34 | 'ordering': ('-published',), 35 | }, 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /django/blog/migrations/0002_auto_20200914_1537.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-14 14:37 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('blog', '0001_initial'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name='post', 20 | name='author', 21 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='blog_posts', to=settings.AUTH_USER_MODEL), 22 | ), 23 | migrations.AddField( 24 | model_name='post', 25 | name='category', 26 | field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to='blog.category'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /django/blog/migrations/0003_post_image.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-22 13:35 2 | 3 | import blog.models 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('blog', '0002_auto_20200914_1537'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='post', 16 | name='image', 17 | field=models.ImageField(default='posts/default.jpg', upload_to=blog.models.upload_to, verbose_name='Image'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /django/blog/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/migrations/__init__.py -------------------------------------------------------------------------------- /django/blog/migrations/__pycache__/0001_initial.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/migrations/__pycache__/0001_initial.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog/migrations/__pycache__/0002_auto_20200914_1537.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/migrations/__pycache__/0002_auto_20200914_1537.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog/migrations/__pycache__/0003_post_image.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/migrations/__pycache__/0003_post_image.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog/migrations/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog/migrations/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | from django.conf import settings 4 | from django.utils.translation import gettext_lazy as _ 5 | 6 | 7 | def upload_to(instance, filename): 8 | return 'posts/{filename}'.format(filename=filename) 9 | 10 | 11 | class Category(models.Model): 12 | name = models.CharField(max_length=100) 13 | 14 | def __str__(self): 15 | return self.name 16 | 17 | 18 | class Post(models.Model): 19 | 20 | class PostObjects(models.Manager): 21 | def get_queryset(self): 22 | return super().get_queryset() .filter(status='published') 23 | 24 | options = ( 25 | ('draft', 'Draft'), 26 | ('published', 'Published'), 27 | ) 28 | 29 | category = models.ForeignKey( 30 | Category, on_delete=models.PROTECT, default=1) 31 | title = models.CharField(max_length=250) 32 | image = models.ImageField( 33 | _("Image"), upload_to=upload_to, default='posts/default.jpg') 34 | excerpt = models.TextField(null=True) 35 | content = models.TextField() 36 | slug = models.SlugField(max_length=250, unique_for_date='published') 37 | published = models.DateTimeField(default=timezone.now) 38 | author = models.ForeignKey( 39 | settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='blog_posts') 40 | status = models.CharField( 41 | max_length=10, choices=options, default='published') 42 | objects = models.Manager() # default manager 43 | postobjects = PostObjects() # custom manager 44 | 45 | class Meta: 46 | ordering = ('-published',) 47 | 48 | def __str__(self): 49 | return self.title 50 | -------------------------------------------------------------------------------- /django/blog/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.contrib.auth.models import User 3 | from blog.models import Post, Category 4 | 5 | 6 | class Test_Create_Post(TestCase): 7 | 8 | @classmethod 9 | def setUpTestData(cls): 10 | test_category = Category.objects.create(name='django') 11 | testuser1 = User.objects.create_user( 12 | username='test_user1', password='123456789') 13 | test_post = Post.objects.create(category_id=1, title='Post Title', excerpt='Post Excerpt', 14 | content='Post Content', slug='post-title', author_id=1, status='published') 15 | 16 | def test_blog_content(self): 17 | post = Post.postobjects.get(id=1) 18 | cat = Category.objects.get(id=1) 19 | author = f'{post.author}' 20 | excerpt = f'{post.excerpt}' 21 | title = f'{post.title}' 22 | content = f'{post.content}' 23 | status = f'{post.status}' 24 | self.assertEqual(author, 'test_user1') 25 | self.assertEqual(title, 'Post Title') 26 | self.assertEqual(content, 'Post Content') 27 | self.assertEqual(status, 'published') 28 | self.assertEqual(str(post), "Post Title") 29 | self.assertEqual(str(cat), "django") 30 | -------------------------------------------------------------------------------- /django/blog/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic import TemplateView 3 | 4 | app_name = 'blog' 5 | 6 | urlpatterns = [ 7 | path('', TemplateView.as_view(template_name="blog/index.html")), 8 | ] -------------------------------------------------------------------------------- /django/blog/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /django/blog_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog_api/__init__.py -------------------------------------------------------------------------------- /django/blog_api/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog_api/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog_api/__pycache__/admin.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog_api/__pycache__/admin.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog_api/__pycache__/models.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog_api/__pycache__/models.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog_api/__pycache__/serializers.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog_api/__pycache__/serializers.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog_api/__pycache__/tests.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog_api/__pycache__/tests.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog_api/__pycache__/urls.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog_api/__pycache__/urls.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog_api/__pycache__/views.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog_api/__pycache__/views.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog_api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogApiConfig(AppConfig): 5 | name = 'blog_api' 6 | -------------------------------------------------------------------------------- /django/blog_api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog_api/migrations/__init__.py -------------------------------------------------------------------------------- /django/blog_api/migrations/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/blog_api/migrations/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /django/blog_api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from blog.models import Post 3 | from django.conf import settings 4 | 5 | 6 | class PostSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = Post 9 | fields = ('category', 'id', 'title', 'image', 'slug', 'author', 10 | 'excerpt', 'content', 'status') 11 | 12 | 13 | class UserRegisterSerializer(serializers.ModelSerializer): 14 | 15 | email = serializers.EmailField(required=True) 16 | username = serializers.CharField(required=True) 17 | password = serializers.CharField(min_length=8, write_only=True) 18 | 19 | class Meta: 20 | model = settings.AUTH_USER_MODEL 21 | fields = ('email', 'user_name', 'first_name') 22 | extra_kwargs = {'password': {'write_only': True}} 23 | -------------------------------------------------------------------------------- /django/blog_api/tests.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | from rest_framework import status 3 | from rest_framework.test import APITestCase 4 | from blog.models import Post, Category 5 | from django.contrib.auth.models import User 6 | from rest_framework.test import APIClient 7 | 8 | 9 | class PostTests(APITestCase): 10 | 11 | def test_view_posts(self): 12 | """ 13 | Ensure we can view all objects. 14 | """ 15 | url = reverse('blog_api:listcreate') 16 | response = self.client.get(url, format='json') 17 | self.assertEqual(response.status_code, status.HTTP_200_OK) 18 | 19 | def test_create_post(self): 20 | """ 21 | Ensure we can create a new Post object and view object. 22 | """ 23 | self.test_category = Category.objects.create(name='django') 24 | self.testuser1 = User.objects.create_superuser( 25 | username='test_user1', password='123456789') 26 | # self.testuser1.is_staff = True 27 | 28 | self.client.login(username=self.testuser1.username, 29 | password='123456789') 30 | 31 | data = {"title": "new", "author": 1, 32 | "excerpt": "new", "content": "new"} 33 | url = reverse('blog_api:listcreate') 34 | response = self.client.post(url, data, format='json') 35 | self.assertEqual(response.status_code, status.HTTP_201_CREATED) 36 | 37 | def test_post_update(self): 38 | 39 | client = APIClient() 40 | 41 | self.test_category = Category.objects.create(name='django') 42 | self.testuser1 = User.objects.create_user( 43 | username='test_user1', password='123456789') 44 | self.testuser2 = User.objects.create_user( 45 | username='test_user2', password='123456789') 46 | test_post = Post.objects.create( 47 | category_id=1, title='Post Title', excerpt='Post Excerpt', content='Post Content', slug='post-title', author_id=1, status='published') 48 | 49 | client.login(username=self.testuser1.username, 50 | password='123456789') 51 | 52 | url = reverse(('blog_api:detailcreate'), kwargs={'pk': 1}) 53 | 54 | response = client.put( 55 | url, { 56 | "title": "New", 57 | "author": 1, 58 | "excerpt": "New", 59 | "content": "New", 60 | "status": "published" 61 | }, format='json') 62 | print(response.data) 63 | self.assertEqual(response.status_code, status.HTTP_200_OK) 64 | -------------------------------------------------------------------------------- /django/blog_api/urls.py: -------------------------------------------------------------------------------- 1 | from .views import PostList, PostDetail, PostListDetailfilter, CreatePost, EditPost, AdminPostDetail, DeletePost 2 | from django.urls import path 3 | 4 | app_name = 'blog_api' 5 | 6 | urlpatterns = [ 7 | path('', PostList.as_view(), name='listpost'), 8 | path('post//', PostDetail.as_view(), name='detailpost'), 9 | path('search/', PostListDetailfilter.as_view(), name='searchpost'), 10 | # Post Admin URLs 11 | path('admin/create/', CreatePost.as_view(), name='createpost'), 12 | path('admin/edit/postdetail//', AdminPostDetail.as_view(), name='admindetailpost'), 13 | path('admin/edit//', EditPost.as_view(), name='editpost'), 14 | path('admin/delete//', DeletePost.as_view(), name='deletepost'), 15 | ] 16 | -------------------------------------------------------------------------------- /django/blog_api/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import get_object_or_404 2 | from blog.models import Post 3 | from .serializers import PostSerializer 4 | from rest_framework import viewsets, filters, generics, permissions 5 | from rest_framework.response import Response 6 | from rest_framework.views import APIView 7 | from rest_framework import status 8 | from rest_framework.parsers import MultiPartParser, FormParser 9 | # Display Posts 10 | 11 | 12 | class PostList(generics.ListAPIView): 13 | 14 | serializer_class = PostSerializer 15 | queryset = Post.objects.all() 16 | 17 | 18 | class PostDetail(generics.RetrieveAPIView): 19 | 20 | serializer_class = PostSerializer 21 | 22 | def get_object(self, queryset=None, **kwargs): 23 | item = self.kwargs.get('pk') 24 | return get_object_or_404(Post, slug=item) 25 | 26 | # Post Search 27 | 28 | 29 | class PostListDetailfilter(generics.ListAPIView): 30 | 31 | queryset = Post.objects.all() 32 | serializer_class = PostSerializer 33 | filter_backends = [filters.SearchFilter] 34 | # '^' Starts-with search. 35 | # '=' Exact matches. 36 | search_fields = ['^slug'] 37 | 38 | # Post Admin 39 | 40 | # class CreatePost(generics.CreateAPIView): 41 | # permission_classes = [permissions.IsAuthenticated] 42 | # queryset = Post.objects.all() 43 | # serializer_class = PostSerializer 44 | 45 | 46 | class CreatePost(APIView): 47 | permission_classes = [permissions.IsAuthenticated] 48 | parser_classes = [MultiPartParser, FormParser] 49 | 50 | def post(self, request, format=None): 51 | print(request.data) 52 | serializer = PostSerializer(data=request.data) 53 | if serializer.is_valid(): 54 | serializer.save() 55 | return Response(serializer.data, status=status.HTTP_200_OK) 56 | else: 57 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 58 | 59 | 60 | class AdminPostDetail(generics.RetrieveAPIView): 61 | permission_classes = [permissions.IsAuthenticated] 62 | queryset = Post.objects.all() 63 | serializer_class = PostSerializer 64 | 65 | 66 | class EditPost(generics.UpdateAPIView): 67 | permission_classes = [permissions.IsAuthenticated] 68 | serializer_class = PostSerializer 69 | queryset = Post.objects.all() 70 | 71 | 72 | class DeletePost(generics.RetrieveDestroyAPIView): 73 | permission_classes = [permissions.IsAuthenticated] 74 | serializer_class = PostSerializer 75 | queryset = Post.objects.all() 76 | 77 | 78 | """ Concrete View Classes 79 | # CreateAPIView 80 | Used for create-only endpoints. 81 | # ListAPIView 82 | Used for read-only endpoints to represent a collection of model instances. 83 | # RetrieveAPIView 84 | Used for read-only endpoints to represent a single model instance. 85 | # DestroyAPIView 86 | Used for delete-only endpoints for a single model instance. 87 | # UpdateAPIView 88 | Used for update-only endpoints for a single model instance. 89 | # ListCreateAPIView 90 | Used for read-write endpoints to represent a collection of model instances. 91 | RetrieveUpdateAPIView 92 | Used for read or update endpoints to represent a single model instance. 93 | # RetrieveDestroyAPIView 94 | Used for read or delete endpoints to represent a single model instance. 95 | # RetrieveUpdateDestroyAPIView 96 | Used for read-write-delete endpoints to represent a single model instance. 97 | """ 98 | -------------------------------------------------------------------------------- /django/commands.txt: -------------------------------------------------------------------------------- 1 | Part 1 2 | ============= 3 | py manage.py makemigrations --dry-run --verbosity 3 4 | py manage.py runserver 5 | py manage.py createsuperuser 6 | pip install coverage 7 | coverage run --omit='*/venv/*' manage.py test 8 | coverage html 9 | pip install djangorestframework 10 | ============= 11 | Part 3 12 | pip install djangorestframework-simplejwt 13 | 14 | $ curl -X POST -d "email=a@a.com&password=aa610153" http://localhost:8000/api/token/ 15 | 16 | 17 | pip install django-filter -------------------------------------------------------------------------------- /django/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/core/__init__.py -------------------------------------------------------------------------------- /django/core/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/core/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /django/core/__pycache__/settings.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/core/__pycache__/settings.cpython-38.pyc -------------------------------------------------------------------------------- /django/core/__pycache__/urls.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/core/__pycache__/urls.cpython-38.pyc -------------------------------------------------------------------------------- /django/core/__pycache__/wsgi.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/core/__pycache__/wsgi.cpython-38.pyc -------------------------------------------------------------------------------- /django/core/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for core project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /django/core/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for core project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.1.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | from datetime import timedelta 15 | import os 16 | 17 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 18 | BASE_DIR = Path(__file__).resolve().parent.parent 19 | 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | SECRET_KEY = 'apuvicmef^(!j8gx&clu0u(!8m0r^etok^l0)kc!---#(i5=dt' 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = True 29 | 30 | ALLOWED_HOSTS = [] 31 | 32 | 33 | # Application definition 34 | 35 | INSTALLED_APPS = [ 36 | 'rest_framework', 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | 'blog', 44 | 'blog_api', 45 | 'corsheaders', 46 | 'users', 47 | 'rest_framework_simplejwt.token_blacklist', 48 | ] 49 | 50 | MIDDLEWARE = [ 51 | 'django.middleware.security.SecurityMiddleware', 52 | 'django.contrib.sessions.middleware.SessionMiddleware', 53 | 'corsheaders.middleware.CorsMiddleware', 54 | 'django.middleware.common.CommonMiddleware', 55 | 'django.middleware.csrf.CsrfViewMiddleware', 56 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 57 | 'django.contrib.messages.middleware.MessageMiddleware', 58 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 59 | ] 60 | 61 | ROOT_URLCONF = 'core.urls' 62 | 63 | TEMPLATES = [ 64 | { 65 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 66 | 'DIRS': [BASE_DIR / 'templates'], 67 | 'APP_DIRS': True, 68 | 'OPTIONS': { 69 | 'context_processors': [ 70 | 'django.template.context_processors.debug', 71 | 'django.template.context_processors.request', 72 | 'django.contrib.auth.context_processors.auth', 73 | 'django.contrib.messages.context_processors.messages', 74 | ], 75 | }, 76 | }, 77 | ] 78 | 79 | WSGI_APPLICATION = 'core.wsgi.application' 80 | 81 | 82 | # Database 83 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 84 | 85 | DATABASES = { 86 | 'default': { 87 | 'ENGINE': 'django.db.backends.sqlite3', 88 | 'NAME': BASE_DIR / 'db.sqlite3', 89 | } 90 | } 91 | 92 | 93 | # Password validation 94 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 95 | 96 | AUTH_PASSWORD_VALIDATORS = [ 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 102 | }, 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 105 | }, 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 108 | }, 109 | ] 110 | 111 | 112 | # Internationalization 113 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 114 | 115 | LANGUAGE_CODE = 'en-us' 116 | 117 | TIME_ZONE = 'UTC' 118 | 119 | USE_I18N = True 120 | 121 | USE_L10N = True 122 | 123 | USE_TZ = True 124 | 125 | 126 | # Static files (CSS, JavaScript, Images) 127 | # https://docs.djangoproject.com/en/3.1/howto/static-files/ 128 | 129 | STATIC_URL = '/static/' 130 | 131 | # Permissions: 132 | # AllowAny 133 | # IsAuthenticated 134 | # IsAdminUser 135 | # IsAuthenticatedOrReadOnly 136 | 137 | CORS_ALLOWED_ORIGINS = [ 138 | "http://localhost:3000" 139 | ] 140 | 141 | # Custom user model 142 | AUTH_USER_MODEL = "users.NewUser" 143 | 144 | SIMPLE_JWT = { 145 | 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=1), 146 | 'REFRESH_TOKEN_LIFETIME': timedelta(days=10), 147 | 'ROTATE_REFRESH_TOKENS': True, 148 | 'BLACKLIST_AFTER_ROTATION': True, 149 | 'ALGORITHM': 'HS256', 150 | 'SIGNING_KEY': SECRET_KEY, 151 | 'VERIFYING_KEY': None, 152 | 'AUTH_HEADER_TYPES': ('JWT',), 153 | 'USER_ID_FIELD': 'id', 154 | 'USER_ID_CLAIM': 'user_id', 155 | 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), 156 | 'TOKEN_TYPE_CLAIM': 'token_type', 157 | } 158 | 159 | REST_FRAMEWORK = { 160 | 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', 161 | 'DEFAULT_PERMISSION_CLASSES': [ 162 | 'rest_framework.permissions.AllowAny', 163 | ], 164 | 'DEFAULT_AUTHENTICATION_CLASSES': [ 165 | 'rest_framework_simplejwt.authentication.JWTAuthentication' 166 | ], 167 | } 168 | 169 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 170 | MEDIA_URL = '/media/' 171 | -------------------------------------------------------------------------------- /django/core/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from rest_framework.schemas import get_schema_view 4 | from rest_framework.documentation import include_docs_urls 5 | from rest_framework_simplejwt.views import ( 6 | TokenObtainPairView, 7 | TokenRefreshView, 8 | ) 9 | from django.conf import settings 10 | from django.conf.urls.static import static 11 | 12 | urlpatterns = [ 13 | # API Token Management 14 | path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), 15 | path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), 16 | # Project URLs 17 | path('admin/', admin.site.urls), 18 | path('', include('blog.urls', namespace='blog')), 19 | # User Management 20 | path('api/user/', include('users.urls', namespace='users')), 21 | # Blog_API Application 22 | path('api/', include('blog_api.urls', namespace='blog_api')), 23 | 24 | # API schema and Documentation 25 | path('project/docs/', include_docs_urls(title='BlogAPI')), 26 | path('project/schema', get_schema_view( 27 | title="BlogAPI", 28 | description="API for the BlogAPI", 29 | version="1.0.0" 30 | ), name='openapi-schema'), 31 | ] 32 | 33 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 34 | -------------------------------------------------------------------------------- /django/core/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for core project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /django/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/db.sqlite3 -------------------------------------------------------------------------------- /django/htmlcov/blog___init___py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\__init__.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |
55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /django/htmlcov/blog_admin_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\admin.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.contrib import admin 

55 |

2from . import models 

56 |

3 

57 |

4 

58 |

5@admin.register(models.Post) 

59 |

6class AuthorAdmin(admin.ModelAdmin): 

60 |

7 list_display = ('title', 'id', 'status', 'slug', 'author') 

61 |

8 prepopulated_fields = {'slug': ('title',), } 

62 |

9 

63 |

10 

64 |

11admin.site.register(models.Category) 

65 |
66 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /django/htmlcov/blog_api___init___py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\__init__.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |
55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /django/htmlcov/blog_api_admin_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\admin.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.contrib import admin 

55 |

2 

56 |

3# Register your models here. 

57 |
58 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /django/htmlcov/blog_api_migrations___init___py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\migrations\__init__.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |
55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /django/htmlcov/blog_api_models_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\models.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.db import models 

55 |

2 

56 |

3# Create your models here. 

57 |
58 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /django/htmlcov/blog_api_serializers_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\serializers.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from rest_framework import serializers 

55 |

2from blog.models import Post 

56 |

3 

57 |

4class PostSerializer(serializers.ModelSerializer): 

58 |

5 class Meta: 

59 |

6 model = Post 

60 |

7 fields = ('id', 'title', 'author', 'excerpt', 'content', 'status') 

61 |
62 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /django/htmlcov/blog_api_tests_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\tests.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.test import TestCase 

55 |

2 

56 |

3# Create your tests here. 

57 |
58 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /django/htmlcov/blog_api_urls_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\urls.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.urls import path 

55 |

2from .views import PostList, PostDetail 

56 |

3 

57 |

4app_name = 'blog_api' 

58 |

5 

59 |

6urlpatterns = [ 

60 |

7 path('<int:pk>/', PostDetail.as_view(), name='detailcreate'), 

61 |

8 path('', PostList.as_view(), name='listcreate'), 

62 |

9] 

63 |
64 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /django/htmlcov/blog_api_views_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog_api\views.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from rest_framework import generics 

55 |

2from blog.models import Post 

56 |

3from .serializers import PostSerializer 

57 |

4 

58 |

5 

59 |

6class PostList(generics.ListCreateAPIView): 

60 |

7 queryset = Post.postobjects.all() 

61 |

8 serializer_class = PostSerializer 

62 |

9 

63 |

10 

64 |

11class PostDetail(generics.RetrieveAPIView): 

65 |

12 queryset = Post.objects.all() 

66 |

13 serializer_class = PostSerializer 

67 |

14 

68 |

15 

69 |

16""" Concrete View Classes 

70 |

17#CreateAPIView 

71 |

18Used for create-only endpoints. 

72 |

19#ListAPIView 

73 |

20Used for read-only endpoints to represent a collection of model instances. 

74 |

21#RetrieveAPIView 

75 |

22Used for read-only endpoints to represent a single model instance. 

76 |

23#DestroyAPIView 

77 |

24Used for delete-only endpoints for a single model instance. 

78 |

25#UpdateAPIView 

79 |

26Used for update-only endpoints for a single model instance. 

80 |

27##ListCreateAPIView 

81 |

28Used for read-write endpoints to represent a collection of model instances. 

82 |

29RetrieveUpdateAPIView 

83 |

30Used for read or update endpoints to represent a single model instance. 

84 |

31#RetrieveDestroyAPIView 

85 |

32Used for read or delete endpoints to represent a single model instance. 

86 |

33#RetrieveUpdateDestroyAPIView 

87 |

34Used for read-write-delete endpoints to represent a single model instance. 

88 |

35""" 

89 |
90 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /django/htmlcov/blog_migrations___init___py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\migrations\__init__.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |
55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /django/htmlcov/blog_tests_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\tests.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.test import TestCase 

55 |

2from django.contrib.auth.models import User 

56 |

3from blog.models import Post, Category 

57 |

4 

58 |

5 

59 |

6class Test_Create_Post(TestCase): 

60 |

7 

61 |

8 @classmethod 

62 |

9 def setUpTestData(cls): 

63 |

10 test_category = Category.objects.create(name='django') 

64 |

11 testuser1 = User.objects.create_user( 

65 |

12 username='test_user1', password='123456789') 

66 |

13 test_post = Post.objects.create(category_id=1, title='Post Title', excerpt='Post Excerpt', 

67 |

14 content='Post Content', slug='post-title', author_id=1, status='published') 

68 |

15 

69 |

16 def test_blog_content(self): 

70 |

17 post = Post.postobjects.get(id=1) 

71 |

18 cat = Category.objects.get(id=1) 

72 |

19 author = f'{post.author}' 

73 |

20 excerpt = f'{post.excerpt}' 

74 |

21 title = f'{post.title}' 

75 |

22 content = f'{post.content}' 

76 |

23 status = f'{post.status}' 

77 |

24 self.assertEqual(author, 'test_user1') 

78 |

25 self.assertEqual(title, 'Post Title') 

79 |

26 self.assertEqual(content, 'Post Content') 

80 |

27 self.assertEqual(status, 'published') 

81 |

28 self.assertEqual(str(post), "Post Title") 

82 |

29 self.assertEqual(str(cat), "django") 

83 |
84 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /django/htmlcov/blog_urls_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for blog\urls.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.urls import path 

55 |

2from django.views.generic import TemplateView 

56 |

3 

57 |

4app_name = 'blog' 

58 |

5 

59 |

6urlpatterns = [ 

60 |

7 path('', TemplateView.as_view(template_name="blog/index.html")), 

61 |

8] 

62 |
63 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /django/htmlcov/core___init___py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for core\__init__.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |
55 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /django/htmlcov/core_urls_py.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coverage for core\urls.py: 100% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1from django.contrib import admin 

55 |

2from django.urls import path, include 

56 |

3 

57 |

4urlpatterns = [ 

58 |

5 path('admin/', admin.site.urls), 

59 |

6 path('', include('blog.urls', namespace='blog')), 

60 |

7 path('api/', include('blog_api.urls', namespace='blog_api')), 

61 |

8] 

62 |
63 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /django/htmlcov/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Coverage report 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 28 |
29 | Hide keyboard shortcuts 30 |

Hot-keys on this page

31 |
32 |

33 | n 34 | s 35 | m 36 | x 37 | c   change column sorting 38 |

39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |
Modulestatementsmissingexcludedcoverage
Total1252098%
blog\__init__.py000100%
blog\admin.py700100%
blog\migrations\0001_initial.py800100%
blog\migrations\__init__.py000100%
blog\models.py2600100%
blog\tests.py2300100%
blog\urls.py400100%
blog_api\__init__.py000100%
blog_api\admin.py100100%
blog_api\migrations\__init__.py000100%
blog_api\models.py100100%
blog_api\serializers.py600100%
blog_api\tests.py100100%
blog_api\urls.py400100%
blog_api\views.py1000100%
core\__init__.py000100%
core\settings.py1900100%
core\urls.py300100%
manage.py122083%
197 |

198 | No items found using the specified filter. 199 |

200 |
201 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /django/htmlcov/jquery.ba-throttle-debounce.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery throttle / debounce - v1.1 - 3/7/2010 3 | * http://benalman.com/projects/jquery-throttle-debounce-plugin/ 4 | * 5 | * Copyright (c) 2010 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!=="boolean"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this); 10 | -------------------------------------------------------------------------------- /django/htmlcov/jquery.hotkeys.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Hotkeys Plugin 3 | * Copyright 2010, John Resig 4 | * Dual licensed under the MIT or GPL Version 2 licenses. 5 | * 6 | * Based upon the plugin by Tzury Bar Yochay: 7 | * http://github.com/tzuryby/hotkeys 8 | * 9 | * Original idea by: 10 | * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ 11 | */ 12 | 13 | (function(jQuery){ 14 | 15 | jQuery.hotkeys = { 16 | version: "0.8", 17 | 18 | specialKeys: { 19 | 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", 20 | 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", 21 | 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 22 | 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", 23 | 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", 24 | 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", 25 | 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" 26 | }, 27 | 28 | shiftNums: { 29 | "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", 30 | "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", 31 | ".": ">", "/": "?", "\\": "|" 32 | } 33 | }; 34 | 35 | function keyHandler( handleObj ) { 36 | // Only care when a possible input has been specified 37 | if ( typeof handleObj.data !== "string" ) { 38 | return; 39 | } 40 | 41 | var origHandler = handleObj.handler, 42 | keys = handleObj.data.toLowerCase().split(" "); 43 | 44 | handleObj.handler = function( event ) { 45 | // Don't fire in text-accepting inputs that we didn't directly bind to 46 | if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || 47 | event.target.type === "text") ) { 48 | return; 49 | } 50 | 51 | // Keypress represents characters, not special keys 52 | var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], 53 | character = String.fromCharCode( event.which ).toLowerCase(), 54 | key, modif = "", possible = {}; 55 | 56 | // check combinations (alt|ctrl|shift+anything) 57 | if ( event.altKey && special !== "alt" ) { 58 | modif += "alt+"; 59 | } 60 | 61 | if ( event.ctrlKey && special !== "ctrl" ) { 62 | modif += "ctrl+"; 63 | } 64 | 65 | // TODO: Need to make sure this works consistently across platforms 66 | if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { 67 | modif += "meta+"; 68 | } 69 | 70 | if ( event.shiftKey && special !== "shift" ) { 71 | modif += "shift+"; 72 | } 73 | 74 | if ( special ) { 75 | possible[ modif + special ] = true; 76 | 77 | } else { 78 | possible[ modif + character ] = true; 79 | possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; 80 | 81 | // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" 82 | if ( modif === "shift+" ) { 83 | possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; 84 | } 85 | } 86 | 87 | for ( var i = 0, l = keys.length; i < l; i++ ) { 88 | if ( possible[ keys[i] ] ) { 89 | return origHandler.apply( this, arguments ); 90 | } 91 | } 92 | }; 93 | } 94 | 95 | jQuery.each([ "keydown", "keyup", "keypress" ], function() { 96 | jQuery.event.special[ this ] = { add: keyHandler }; 97 | }); 98 | 99 | })( jQuery ); 100 | -------------------------------------------------------------------------------- /django/htmlcov/jquery.isonscreen.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010 2 | * @author Laurence Wheway 3 | * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 4 | * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. 5 | * 6 | * @version 1.2.0 7 | */ 8 | (function($) { 9 | jQuery.extend({ 10 | isOnScreen: function(box, container) { 11 | //ensure numbers come in as intgers (not strings) and remove 'px' is it's there 12 | for(var i in box){box[i] = parseFloat(box[i])}; 13 | for(var i in container){container[i] = parseFloat(container[i])}; 14 | 15 | if(!container){ 16 | container = { 17 | left: $(window).scrollLeft(), 18 | top: $(window).scrollTop(), 19 | width: $(window).width(), 20 | height: $(window).height() 21 | } 22 | } 23 | 24 | if( box.left+box.width-container.left > 0 && 25 | box.left < container.width+container.left && 26 | box.top+box.height-container.top > 0 && 27 | box.top < container.height+container.top 28 | ) return true; 29 | return false; 30 | } 31 | }) 32 | 33 | 34 | jQuery.fn.isOnScreen = function (container) { 35 | for(var i in container){container[i] = parseFloat(container[i])}; 36 | 37 | if(!container){ 38 | container = { 39 | left: $(window).scrollLeft(), 40 | top: $(window).scrollTop(), 41 | width: $(window).width(), 42 | height: $(window).height() 43 | } 44 | } 45 | 46 | if( $(this).offset().left+$(this).width()-container.left > 0 && 47 | $(this).offset().left < container.width+container.left && 48 | $(this).offset().top+$(this).height()-container.top > 0 && 49 | $(this).offset().top < container.height+container.top 50 | ) return true; 51 | return false; 52 | } 53 | })(jQuery); 54 | -------------------------------------------------------------------------------- /django/htmlcov/jquery.tablesorter.min.js: -------------------------------------------------------------------------------- 1 | 2 | (function($){$.extend({tablesorter:new function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'.',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}var rows=table.tBodies[0].rows;if(table.tBodies[0].rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;ib)?1:0));};function sortTextDesc(a,b){return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){$this.trigger("sortStart");var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){var $cell=$(this);var i=this.column;this.order=this.count++%2;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i 2 | 3 | 4 | 5 | 6 | Coverage for manage.py: 83% 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 31 |
32 | Hide keyboard shortcuts 33 |

Hot-keys on this page

34 |
35 |

36 | r 37 | m 38 | x 39 | p   toggle line displays 40 |

41 |

42 | j 43 | k   next/prev highlighted chunk 44 |

45 |

46 | 0   (zero) top of page 47 |

48 |

49 | 1   (one) first highlighted chunk 50 |

51 |
52 |
53 |
54 |

1#!/usr/bin/env python 

55 |

2"""Django's command-line utility for administrative tasks.""" 

56 |

3import os 

57 |

4import sys 

58 |

5 

59 |

6 

60 |

7def main(): 

61 |

8 """Run administrative tasks.""" 

62 |

9 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 

63 |

10 try: 

64 |

11 from django.core.management import execute_from_command_line 

65 |

12 except ImportError as exc: 

66 |

13 raise ImportError( 

67 |

14 "Couldn't import Django. Are you sure it's installed and " 

68 |

15 "available on your PYTHONPATH environment variable? Did you " 

69 |

16 "forget to activate a virtual environment?" 

70 |

17 ) from exc 

71 |

18 execute_from_command_line(sys.argv) 

72 |

19 

73 |

20 

74 |

21if __name__ == '__main__': 

75 |

22 main() 

76 |
77 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /django/htmlcov/status.json: -------------------------------------------------------------------------------- 1 | {"format":2,"version":"5.2.1","globals":"c3fc29d529a3af98c1a2065daa2addf6","files":{"blog___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"blog___init___py.html","relative_filename":"blog\\__init__.py"}},"blog_admin_py":{"hash":"c4a66b1f102077f89e36517da546c95e","index":{"nums":[1,7,0,0,0,0,0],"html_filename":"blog_admin_py.html","relative_filename":"blog\\admin.py"}},"blog_migrations___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"blog_migrations___init___py.html","relative_filename":"blog\\migrations\\__init__.py"}},"blog_models_py":{"hash":"03ffa1ed7c73ebd42b64441405beea0a","index":{"nums":[1,26,0,0,0,0,0],"html_filename":"blog_models_py.html","relative_filename":"blog\\models.py"}},"blog_tests_py":{"hash":"e3d96d1c1c54be7e81a1b30a56bc2811","index":{"nums":[1,23,0,0,0,0,0],"html_filename":"blog_tests_py.html","relative_filename":"blog\\tests.py"}},"blog_urls_py":{"hash":"5de25e221f5f526ff48181ca0726b942","index":{"nums":[1,4,0,0,0,0,0],"html_filename":"blog_urls_py.html","relative_filename":"blog\\urls.py"}},"blog_api___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"blog_api___init___py.html","relative_filename":"blog_api\\__init__.py"}},"blog_api_admin_py":{"hash":"23b3f8ab894286afb6416200cf284c31","index":{"nums":[1,1,0,0,0,0,0],"html_filename":"blog_api_admin_py.html","relative_filename":"blog_api\\admin.py"}},"blog_api_migrations___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"blog_api_migrations___init___py.html","relative_filename":"blog_api\\migrations\\__init__.py"}},"blog_api_models_py":{"hash":"d1fe3410689f990be0aed25fad2be2c0","index":{"nums":[1,1,0,0,0,0,0],"html_filename":"blog_api_models_py.html","relative_filename":"blog_api\\models.py"}},"blog_api_serializers_py":{"hash":"62cc5cdad3c7e087b57c54036357802d","index":{"nums":[1,6,0,0,0,0,0],"html_filename":"blog_api_serializers_py.html","relative_filename":"blog_api\\serializers.py"}},"blog_api_tests_py":{"hash":"f19c76500f700766a5a7685bbf253a0f","index":{"nums":[1,1,0,0,0,0,0],"html_filename":"blog_api_tests_py.html","relative_filename":"blog_api\\tests.py"}},"blog_api_urls_py":{"hash":"d4a7c0b9d5213327f3d2e1817d5c0fae","index":{"nums":[1,4,0,0,0,0,0],"html_filename":"blog_api_urls_py.html","relative_filename":"blog_api\\urls.py"}},"blog_api_views_py":{"hash":"8111d2abe2a770e77f568afb8fbc8e79","index":{"nums":[1,10,0,0,0,0,0],"html_filename":"blog_api_views_py.html","relative_filename":"blog_api\\views.py"}},"core___init___py":{"hash":"af9d0d0de0ea3b71d198bc63f1499a7d","index":{"nums":[1,0,0,0,0,0,0],"html_filename":"core___init___py.html","relative_filename":"core\\__init__.py"}},"core_settings_py":{"hash":"33b3221df0cf35936e2298a8f67b8dd4","index":{"nums":[1,19,0,0,0,0,0],"html_filename":"core_settings_py.html","relative_filename":"core\\settings.py"}},"core_urls_py":{"hash":"23c5407846818bef9a012f5f36b91dfd","index":{"nums":[1,3,0,0,0,0,0],"html_filename":"core_urls_py.html","relative_filename":"core\\urls.py"}},"manage_py":{"hash":"f8c6d49629b8856f7f764a87376dfe41","index":{"nums":[1,12,0,2,0,0,0],"html_filename":"manage_py.html","relative_filename":"manage.py"}},"blog_migrations_0001_initial_py":{"hash":"d628aa3eab1ab439a0f86f40d58265f6","index":{"nums":[1,8,0,0,0,0,0],"html_filename":"blog_migrations_0001_initial_py.html","relative_filename":"blog\\migrations\\0001_initial.py"}}}} -------------------------------------------------------------------------------- /django/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /django/media/posts/asd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/media/posts/asd.png -------------------------------------------------------------------------------- /django/media/posts/asd_1ibJffY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/media/posts/asd_1ibJffY.png -------------------------------------------------------------------------------- /django/media/posts/asd_Cu4QEph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/media/posts/asd_Cu4QEph.png -------------------------------------------------------------------------------- /django/media/posts/asd_fE8DF1X.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/media/posts/asd_fE8DF1X.png -------------------------------------------------------------------------------- /django/media/posts/asd_lRgFpPm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/media/posts/asd_lRgFpPm.png -------------------------------------------------------------------------------- /django/media/posts/asd_lxjs3Le.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/media/posts/asd_lxjs3Le.png -------------------------------------------------------------------------------- /django/media/posts/asd_oruSJ12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/media/posts/asd_oruSJ12.png -------------------------------------------------------------------------------- /django/media/posts/asd_xUsAVvC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/media/posts/asd_xUsAVvC.png -------------------------------------------------------------------------------- /django/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.2.10 2 | certifi==2020.6.20 3 | chardet==3.0.4 4 | coreapi==2.3.3 5 | coreschema==0.0.4 6 | Django==3.1.1 7 | django-cors-headers==3.5.0 8 | djangorestframework==3.11.1 9 | djangorestframework-simplejwt==4.4.0 10 | idna==2.10 11 | itypes==1.2.0 12 | Jinja2==2.11.2 13 | MarkupSafe==1.1.1 14 | Pillow==7.2.0 15 | PyJWT==1.7.1 16 | pytz==2020.1 17 | requests==2.24.0 18 | sqlparse==0.3.1 19 | uritemplate==3.0.1 20 | urllib3==1.25.10 21 | -------------------------------------------------------------------------------- /django/templates/blog/index.html: -------------------------------------------------------------------------------- 1 | //index -------------------------------------------------------------------------------- /django/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/users/__init__.py -------------------------------------------------------------------------------- /django/users/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/users/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /django/users/__pycache__/admin.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/users/__pycache__/admin.cpython-38.pyc -------------------------------------------------------------------------------- /django/users/__pycache__/models.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/users/__pycache__/models.cpython-38.pyc -------------------------------------------------------------------------------- /django/users/__pycache__/serializers.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/users/__pycache__/serializers.cpython-38.pyc -------------------------------------------------------------------------------- /django/users/__pycache__/urls.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/users/__pycache__/urls.cpython-38.pyc -------------------------------------------------------------------------------- /django/users/__pycache__/views.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/users/__pycache__/views.cpython-38.pyc -------------------------------------------------------------------------------- /django/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from users.models import NewUser 3 | from django.contrib.auth.admin import UserAdmin 4 | from django.forms import TextInput, Textarea, CharField 5 | from django import forms 6 | from django.db import models 7 | 8 | 9 | class UserAdminConfig(UserAdmin): 10 | model = NewUser 11 | search_fields = ('email', 'user_name', 'first_name',) 12 | list_filter = ('email', 'user_name', 'first_name', 'is_active', 'is_staff') 13 | ordering = ('-start_date',) 14 | list_display = ('email', 'user_name', 'first_name', 15 | 'is_active', 'is_staff') 16 | fieldsets = ( 17 | (None, {'fields': ('email', 'user_name', 'first_name',)}), 18 | ('Permissions', {'fields': ('is_staff', 'is_active')}), 19 | ('Personal', {'fields': ('about',)}), 20 | ) 21 | formfield_overrides = { 22 | models.TextField: {'widget': Textarea(attrs={'rows': 20, 'cols': 60})}, 23 | } 24 | add_fieldsets = ( 25 | (None, { 26 | 'classes': ('wide',), 27 | 'fields': ('email', 'user_name', 'first_name', 'password1', 'password2', 'is_active', 'is_staff')} 28 | ), 29 | ) 30 | 31 | 32 | admin.site.register(NewUser, UserAdminConfig) 33 | -------------------------------------------------------------------------------- /django/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | name = 'users' 6 | -------------------------------------------------------------------------------- /django/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.1 on 2020-09-14 14:37 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ('auth', '0012_alter_user_first_name_max_length'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='NewUser', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('password', models.CharField(max_length=128, verbose_name='password')), 21 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 22 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 23 | ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), 24 | ('user_name', models.CharField(max_length=150, unique=True)), 25 | ('first_name', models.CharField(blank=True, max_length=150)), 26 | ('start_date', models.DateTimeField(default=django.utils.timezone.now)), 27 | ('about', models.TextField(blank=True, max_length=500, verbose_name='about')), 28 | ('is_staff', models.BooleanField(default=False)), 29 | ('is_active', models.BooleanField(default=False)), 30 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 31 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 32 | ], 33 | options={ 34 | 'abstract': False, 35 | }, 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /django/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/users/migrations/__init__.py -------------------------------------------------------------------------------- /django/users/migrations/__pycache__/0001_initial.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/users/migrations/__pycache__/0001_initial.cpython-38.pyc -------------------------------------------------------------------------------- /django/users/migrations/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/django/users/migrations/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /django/users/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | from django.utils.translation import gettext_lazy as _ 4 | from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager 5 | 6 | 7 | class CustomAccountManager(BaseUserManager): 8 | 9 | def create_superuser(self, email, user_name, first_name, password, **other_fields): 10 | 11 | other_fields.setdefault('is_staff', True) 12 | other_fields.setdefault('is_superuser', True) 13 | other_fields.setdefault('is_active', True) 14 | 15 | if other_fields.get('is_staff') is not True: 16 | raise ValueError( 17 | 'Superuser must be assigned to is_staff=True.') 18 | if other_fields.get('is_superuser') is not True: 19 | raise ValueError( 20 | 'Superuser must be assigned to is_superuser=True.') 21 | 22 | return self.create_user(email, user_name, first_name, password, **other_fields) 23 | 24 | def create_user(self, email, user_name, first_name, password, **other_fields): 25 | 26 | if not email: 27 | raise ValueError(_('You must provide an email address')) 28 | 29 | email = self.normalize_email(email) 30 | user = self.model(email=email, user_name=user_name, 31 | first_name=first_name, **other_fields) 32 | user.set_password(password) 33 | user.save() 34 | return user 35 | 36 | 37 | class NewUser(AbstractBaseUser, PermissionsMixin): 38 | 39 | email = models.EmailField(_('email address'), unique=True) 40 | user_name = models.CharField(max_length=150, unique=True) 41 | first_name = models.CharField(max_length=150, blank=True) 42 | start_date = models.DateTimeField(default=timezone.now) 43 | about = models.TextField(_( 44 | 'about'), max_length=500, blank=True) 45 | is_staff = models.BooleanField(default=False) 46 | is_active = models.BooleanField(default=False) 47 | 48 | objects = CustomAccountManager() 49 | 50 | USERNAME_FIELD = 'email' 51 | REQUIRED_FIELDS = ['user_name', 'first_name'] 52 | 53 | def __str__(self): 54 | return self.user_name 55 | -------------------------------------------------------------------------------- /django/users/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from users.models import NewUser 3 | 4 | 5 | class CustomUserSerializer(serializers.ModelSerializer): 6 | """ 7 | Currently unused in preference of the below. 8 | """ 9 | email = serializers.EmailField(required=True) 10 | user_name = serializers.CharField(required=True) 11 | password = serializers.CharField(min_length=8, write_only=True) 12 | 13 | class Meta: 14 | model = NewUser 15 | fields = ('email', 'user_name', 'password') 16 | extra_kwargs = {'password': {'write_only': True}} 17 | 18 | def create(self, validated_data): 19 | password = validated_data.pop('password', None) 20 | # as long as the fields are the same, we can just use this 21 | instance = self.Meta.model(**validated_data) 22 | if password is not None: 23 | instance.set_password(password) 24 | instance.save() 25 | return instance 26 | -------------------------------------------------------------------------------- /django/users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /django/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import CustomUserCreate, BlacklistTokenUpdateView 3 | 4 | app_name = 'users' 5 | 6 | urlpatterns = [ 7 | path('create/', CustomUserCreate.as_view(), name="create_user"), 8 | path('logout/blacklist/', BlacklistTokenUpdateView.as_view(), 9 | name='blacklist') 10 | ] 11 | -------------------------------------------------------------------------------- /django/users/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework_simplejwt.views import TokenObtainPairView 2 | from rest_framework import status 3 | from rest_framework.response import Response 4 | from rest_framework.views import APIView 5 | from .serializers import CustomUserSerializer 6 | from rest_framework_simplejwt.tokens import RefreshToken 7 | from rest_framework.permissions import AllowAny 8 | 9 | 10 | class CustomUserCreate(APIView): 11 | permission_classes = [AllowAny] 12 | 13 | def post(self, request, format='json'): 14 | serializer = CustomUserSerializer(data=request.data) 15 | if serializer.is_valid(): 16 | user = serializer.save() 17 | if user: 18 | json = serializer.data 19 | return Response(json, status=status.HTTP_201_CREATED) 20 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 21 | 22 | 23 | class BlacklistTokenUpdateView(APIView): 24 | permission_classes = [AllowAny] 25 | authentication_classes = () 26 | 27 | def post(self, request): 28 | try: 29 | refresh_token = request.data["refresh_token"] 30 | token = RefreshToken(refresh_token) 31 | token.blacklist() 32 | return Response(status=status.HTTP_205_RESET_CONTENT) 33 | except Exception as e: 34 | return Response(status=status.HTTP_400_BAD_REQUEST) 35 | -------------------------------------------------------------------------------- /react/blogapi/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /react/blogapi/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `npm run build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /react/blogapi/commands.txt: -------------------------------------------------------------------------------- 1 | npm install @material-ui/icons 2 | npm i --save material-ui-search-bar -------------------------------------------------------------------------------- /react/blogapi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blogapi", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.0", 7 | "@material-ui/icons": "^4.9.1", 8 | "@testing-library/jest-dom": "^4.2.4", 9 | "@testing-library/react": "^9.5.0", 10 | "@testing-library/user-event": "^7.2.1", 11 | "axios": "^0.20.0", 12 | "material-ui-search-bar": "^1.0.0", 13 | "react": "^16.13.1", 14 | "react-dom": "^16.13.1", 15 | "react-router-dom": "^5.2.0", 16 | "react-scripts": "3.4.3" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": "react-app" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /react/blogapi/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/react/blogapi/public/favicon.ico -------------------------------------------------------------------------------- /react/blogapi/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /react/blogapi/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/react/blogapi/public/logo192.png -------------------------------------------------------------------------------- /react/blogapi/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/veryacademy/YT-Django-DRF-Simple-Blog-Series-File-Uploading-Part-8/4d7080345c0a2b24b18a4e8f9e8990ebe276fdb9/react/blogapi/public/logo512.png -------------------------------------------------------------------------------- /react/blogapi/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /react/blogapi/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /react/blogapi/src/Admin.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './App.css'; 3 | import Posts from './components/admin/posts'; 4 | import PostLoadingComponent from './components/posts/postLoading'; 5 | import axiosInstance from './axios'; 6 | 7 | function Admin() { 8 | const PostLoading = PostLoadingComponent(Posts); 9 | const [appState, setAppState] = useState({ 10 | loading: true, 11 | posts: null, 12 | }); 13 | 14 | useEffect(() => { 15 | axiosInstance.get().then((res) => { 16 | const allPosts = res.data; 17 | setAppState({ loading: false, posts: allPosts }); 18 | console.log(res.data); 19 | }); 20 | }, [setAppState]); 21 | 22 | return ( 23 |
24 |

Latest Posts

25 | 26 |
27 | ); 28 | } 29 | export default Admin; 30 | -------------------------------------------------------------------------------- /react/blogapi/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /react/blogapi/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './App.css'; 3 | import Posts from './components/posts/posts'; 4 | import PostLoadingComponent from './components/posts/postLoading'; 5 | import axiosInstance from './axios'; 6 | 7 | function App() { 8 | const PostLoading = PostLoadingComponent(Posts); 9 | const [appState, setAppState] = useState({ 10 | loading: true, 11 | posts: null, 12 | }); 13 | 14 | useEffect(() => { 15 | axiosInstance.get().then((res) => { 16 | const allPosts = res.data; 17 | console.log(res.data); 18 | setAppState({ loading: false, posts: allPosts }); 19 | console.log(res.data); 20 | }); 21 | }, [setAppState]); 22 | return ( 23 |
24 |

Latest Posts

25 | 26 |
27 | ); 28 | } 29 | export default App; 30 | -------------------------------------------------------------------------------- /react/blogapi/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /react/blogapi/src/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const baseURL = 'http://127.0.0.1:8000/api/'; 4 | 5 | const axiosInstance = axios.create({ 6 | baseURL: baseURL, 7 | timeout: 5000, 8 | headers: { 9 | Authorization: localStorage.getItem('access_token') 10 | ? 'JWT ' + localStorage.getItem('access_token') 11 | : null, 12 | 'Content-Type': 'application/json', 13 | accept: 'application/json', 14 | }, 15 | }); 16 | 17 | axiosInstance.interceptors.response.use( 18 | (response) => { 19 | return response; 20 | }, 21 | async function (error) { 22 | const originalRequest = error.config; 23 | 24 | if (typeof error.response === 'undefined') { 25 | alert( 26 | 'A server/network error occurred. ' + 27 | 'Looks like CORS might be the problem. ' + 28 | 'Sorry about this - we will get it fixed shortly.' 29 | ); 30 | return Promise.reject(error); 31 | } 32 | 33 | if ( 34 | error.response.status === 401 && 35 | originalRequest.url === baseURL + 'token/refresh/' 36 | ) { 37 | window.location.href = '/login/'; 38 | return Promise.reject(error); 39 | } 40 | 41 | if ( 42 | error.response.data.code === 'token_not_valid' && 43 | error.response.status === 401 && 44 | error.response.statusText === 'Unauthorized' 45 | ) { 46 | const refreshToken = localStorage.getItem('refresh_token'); 47 | 48 | if (refreshToken) { 49 | const tokenParts = JSON.parse(atob(refreshToken.split('.')[1])); 50 | 51 | // exp date in token is expressed in seconds, while now() returns milliseconds: 52 | const now = Math.ceil(Date.now() / 1000); 53 | console.log(tokenParts.exp); 54 | 55 | if (tokenParts.exp > now) { 56 | return axiosInstance 57 | .post('/token/refresh/', { 58 | refresh: refreshToken, 59 | }) 60 | .then((response) => { 61 | localStorage.setItem('access_token', response.data.access); 62 | localStorage.setItem('refresh_token', response.data.refresh); 63 | 64 | axiosInstance.defaults.headers['Authorization'] = 65 | 'JWT ' + response.data.access; 66 | originalRequest.headers['Authorization'] = 67 | 'JWT ' + response.data.access; 68 | 69 | return axiosInstance(originalRequest); 70 | }) 71 | .catch((err) => { 72 | console.log(err); 73 | }); 74 | } else { 75 | console.log('Refresh token is expired', tokenParts.exp, now); 76 | window.location.href = '/login/'; 77 | } 78 | } else { 79 | console.log('Refresh token not available.'); 80 | window.location.href = '/login/'; 81 | } 82 | } 83 | 84 | // specific error handling done elsewhere 85 | return Promise.reject(error); 86 | } 87 | ); 88 | 89 | export default axiosInstance; 90 | -------------------------------------------------------------------------------- /react/blogapi/src/components/admin/create.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import axiosInstance from '../../axios'; 3 | import { useHistory } from 'react-router-dom'; 4 | //MaterialUI 5 | import Avatar from '@material-ui/core/Avatar'; 6 | import Button from '@material-ui/core/Button'; 7 | import CssBaseline from '@material-ui/core/CssBaseline'; 8 | import TextField from '@material-ui/core/TextField'; 9 | import Grid from '@material-ui/core/Grid'; 10 | import Typography from '@material-ui/core/Typography'; 11 | import { makeStyles } from '@material-ui/core/styles'; 12 | import Container from '@material-ui/core/Container'; 13 | import IconButton from '@material-ui/core/IconButton'; 14 | import PhotoCamera from '@material-ui/icons/PhotoCamera'; 15 | 16 | const useStyles = makeStyles((theme) => ({ 17 | paper: { 18 | marginTop: theme.spacing(8), 19 | display: 'flex', 20 | flexDirection: 'column', 21 | alignItems: 'center', 22 | }, 23 | avatar: { 24 | margin: theme.spacing(1), 25 | backgroundColor: theme.palette.secondary.main, 26 | }, 27 | form: { 28 | width: '100%', // Fix IE 11 issue. 29 | marginTop: theme.spacing(3), 30 | }, 31 | submit: { 32 | margin: theme.spacing(3, 0, 2), 33 | }, 34 | })); 35 | 36 | export default function Create() { 37 | //https://gist.github.com/hagemann/382adfc57adbd5af078dc93feef01fe1 38 | function slugify(string) { 39 | const a = 40 | 'àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;'; 41 | const b = 42 | 'aaaaaaaaaacccddeeeeeeeegghiiiiiilmnnnnoooooooooprrsssssttuuuuuuuuuwxyyzzz------'; 43 | const p = new RegExp(a.split('').join('|'), 'g'); 44 | 45 | return string 46 | .toString() 47 | .toLowerCase() 48 | .replace(/\s+/g, '-') // Replace spaces with - 49 | .replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters 50 | .replace(/&/g, '-and-') // Replace & with 'and' 51 | .replace(/[^\w\-]+/g, '') // Remove all non-word characters 52 | .replace(/\-\-+/g, '-') // Replace multiple - with single - 53 | .replace(/^-+/, '') // Trim - from start of text 54 | .replace(/-+$/, ''); // Trim - from end of text 55 | } 56 | // 57 | 58 | const history = useHistory(); 59 | const initialFormData = Object.freeze({ 60 | title: '', 61 | slug: '', 62 | excerpt: '', 63 | content: '', 64 | }); 65 | 66 | const [postData, updateFormData] = useState(initialFormData); 67 | const [postimage, setPostImage] = useState(null); 68 | 69 | const handleChange = (e) => { 70 | if ([e.target.name] == 'image') { 71 | setPostImage({ 72 | image: e.target.files, 73 | }); 74 | console.log(e.target.files); 75 | } 76 | if ([e.target.name] == 'title') { 77 | updateFormData({ 78 | ...postData, 79 | [e.target.name]: e.target.value.trim(), 80 | ['slug']: slugify(e.target.value.trim()), 81 | }); 82 | } else { 83 | updateFormData({ 84 | ...postData, 85 | [e.target.name]: e.target.value.trim(), 86 | }); 87 | } 88 | }; 89 | 90 | const handleSubmit = (e) => { 91 | e.preventDefault(); 92 | let formData = new FormData(); 93 | formData.append('title', postData.title); 94 | formData.append('slug', postData.slug); 95 | formData.append('author', 1); 96 | formData.append('excerpt', postData.excerpt); 97 | formData.append('content', postData.content); 98 | formData.append('image', postimage.image[0]); 99 | axiosInstance.post(`admin/create/`, formData); 100 | history.push({ 101 | pathname: '/admin/', 102 | }); 103 | window.location.reload(); 104 | }; 105 | 106 | // const config = { headers: { 'Content-Type': 'multipart/form-data' } }; 107 | // const URL = 'http://127.0.0.1:8000/api/admin/creats/'; 108 | // let formData = new FormData(); 109 | // formData.append('title', postData.title); 110 | // formData.append('slug', postData.slug); 111 | // formData.append('author', 1); 112 | // formData.append('excerpt', postData.excerpt); 113 | // formData.append('content', postData.content); 114 | // formData.append('image', postimage.image[0]); 115 | // axios 116 | // .post(URL, formData, config) 117 | // .then((res) => { 118 | // console.log(res.data); 119 | // }) 120 | // .catch((err) => console.log(err)); 121 | 122 | const classes = useStyles(); 123 | 124 | return ( 125 | 126 | 127 |
128 | 129 | 130 | Create New Post 131 | 132 |
133 | 134 | 135 | 145 | 146 | 147 | 159 | 160 | 161 | 172 | 173 | 174 | 186 | 187 | 195 | 196 | 206 |
207 |
208 |
209 | ); 210 | } 211 | -------------------------------------------------------------------------------- /react/blogapi/src/components/admin/delete.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import axiosInstance from '../../axios'; 3 | import { useHistory, useParams } from 'react-router-dom'; 4 | //MaterialUI 5 | import Container from '@material-ui/core/Container'; 6 | import Button from '@material-ui/core/Button'; 7 | import Box from '@material-ui/core/Box'; 8 | 9 | export default function Create() { 10 | const history = useHistory(); 11 | const { id } = useParams(); 12 | 13 | const handleSubmit = (e) => { 14 | e.preventDefault(); 15 | axiosInstance 16 | .delete('admin/delete/' + id) 17 | .catch(function (error) { 18 | if (error.response) { 19 | console.log(error.response.data); 20 | console.log(error.response.status); 21 | console.log(error.response.headers); 22 | } 23 | }) 24 | .then(function () { 25 | history.push({ 26 | pathname: '/admin/', 27 | }); 28 | window.location.reload(); 29 | }); 30 | }; 31 | 32 | return ( 33 | 34 | 41 | 49 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /react/blogapi/src/components/admin/edit.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import axiosInstance from '../../axios'; 3 | import { useHistory, useParams } from 'react-router-dom'; 4 | //MaterialUI 5 | import Button from '@material-ui/core/Button'; 6 | import CssBaseline from '@material-ui/core/CssBaseline'; 7 | import TextField from '@material-ui/core/TextField'; 8 | import Grid from '@material-ui/core/Grid'; 9 | import Typography from '@material-ui/core/Typography'; 10 | import { makeStyles } from '@material-ui/core/styles'; 11 | import Container from '@material-ui/core/Container'; 12 | 13 | const useStyles = makeStyles((theme) => ({ 14 | paper: { 15 | marginTop: theme.spacing(8), 16 | display: 'flex', 17 | flexDirection: 'column', 18 | alignItems: 'center', 19 | }, 20 | form: { 21 | width: '100%', // Fix IE 11 issue. 22 | marginTop: theme.spacing(3), 23 | }, 24 | submit: { 25 | margin: theme.spacing(3, 0, 2), 26 | }, 27 | })); 28 | 29 | export default function Create() { 30 | const history = useHistory(); 31 | const { id } = useParams(); 32 | const initialFormData = Object.freeze({ 33 | id: '', 34 | title: '', 35 | slug: '', 36 | excerpt: '', 37 | content: '', 38 | }); 39 | 40 | const [formData, updateFormData] = useState(initialFormData); 41 | 42 | useEffect(() => { 43 | axiosInstance.get('admin/edit/postdetail/' + id).then((res) => { 44 | updateFormData({ 45 | ...formData, 46 | ['title']: res.data.title, 47 | ['excerpt']: res.data.excerpt, 48 | ['slug']: res.data.slug, 49 | ['content']: res.data.content, 50 | }); 51 | console.log(res.data); 52 | }); 53 | }, [updateFormData]); 54 | 55 | const handleChange = (e) => { 56 | updateFormData({ 57 | ...formData, 58 | // Trimming any whitespace 59 | [e.target.name]: e.target.value.trim(), 60 | }); 61 | }; 62 | 63 | const handleSubmit = (e) => { 64 | e.preventDefault(); 65 | console.log(formData); 66 | 67 | axiosInstance.put(`admin/edit/` + id + '/', { 68 | title: formData.title, 69 | slug: formData.slug, 70 | author: 1, 71 | excerpt: formData.excerpt, 72 | content: formData.content, 73 | }); 74 | history.push({ 75 | pathname: '/admin/', 76 | }); 77 | window.location.reload(); 78 | }; 79 | 80 | const classes = useStyles(); 81 | 82 | return ( 83 | 84 | 85 |
86 | 87 | Edit Post 88 | 89 |
90 | 91 | 92 | 103 | 104 | 105 | 118 | 119 | 120 | 131 | 132 | 133 | 146 | 147 | 148 | 158 |
159 |
160 |
161 | ); 162 | } 163 | -------------------------------------------------------------------------------- /react/blogapi/src/components/admin/posts.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Container from '@material-ui/core/Container'; 4 | import Link from '@material-ui/core/Link'; 5 | import Paper from '@material-ui/core/Paper'; 6 | import Table from '@material-ui/core/Table'; 7 | import TableBody from '@material-ui/core/TableBody'; 8 | import TableCell from '@material-ui/core/TableCell'; 9 | import TableContainer from '@material-ui/core/TableContainer'; 10 | import TableHead from '@material-ui/core/TableHead'; 11 | import TableRow from '@material-ui/core/TableRow'; 12 | import DeleteForeverIcon from '@material-ui/icons/DeleteForever'; 13 | import EditIcon from '@material-ui/icons/Edit'; 14 | import Button from '@material-ui/core/Button'; 15 | 16 | const useStyles = makeStyles((theme) => ({ 17 | cardMedia: { 18 | paddingTop: '56.25%', // 16:9 19 | }, 20 | link: { 21 | margin: theme.spacing(1, 1.5), 22 | }, 23 | cardHeader: { 24 | backgroundColor: 25 | theme.palette.type === 'light' 26 | ? theme.palette.grey[200] 27 | : theme.palette.grey[700], 28 | }, 29 | postTitle: { 30 | fontSize: '16px', 31 | textAlign: 'left', 32 | }, 33 | postText: { 34 | display: 'flex', 35 | justifyContent: 'left', 36 | alignItems: 'baseline', 37 | fontSize: '12px', 38 | textAlign: 'left', 39 | marginBottom: theme.spacing(2), 40 | }, 41 | })); 42 | 43 | const Posts = (props) => { 44 | const { posts } = props; 45 | const classes = useStyles(); 46 | if (!posts || posts.length === 0) return

Can not find any posts, sorry

; 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Id 56 | Category 57 | Title 58 | Action 59 | 60 | 61 | 62 | {posts.map((post) => { 63 | return ( 64 | 65 | 66 | {post.id} 67 | 68 | {post.category} 69 | 70 | 71 | 76 | {post.title} 77 | 78 | 79 | 80 | 81 | 86 | 87 | 88 | 93 | 94 | 95 | 96 | 97 | ); 98 | })} 99 | 100 | 101 | 108 | 109 | 110 | 111 |
112 |
113 |
114 |
115 |
116 | ); 117 | }; 118 | export default Posts; 119 | -------------------------------------------------------------------------------- /react/blogapi/src/components/auth/login.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import axiosInstance from '../../axios'; 3 | import { useHistory } from 'react-router-dom'; 4 | //MaterialUI 5 | import Avatar from '@material-ui/core/Avatar'; 6 | import Button from '@material-ui/core/Button'; 7 | import CssBaseline from '@material-ui/core/CssBaseline'; 8 | import TextField from '@material-ui/core/TextField'; 9 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 10 | import Checkbox from '@material-ui/core/Checkbox'; 11 | import Link from '@material-ui/core/Link'; 12 | import Grid from '@material-ui/core/Grid'; 13 | import Typography from '@material-ui/core/Typography'; 14 | import { makeStyles } from '@material-ui/core/styles'; 15 | import Container from '@material-ui/core/Container'; 16 | 17 | 18 | const useStyles = makeStyles((theme) => ({ 19 | paper: { 20 | marginTop: theme.spacing(8), 21 | display: 'flex', 22 | flexDirection: 'column', 23 | alignItems: 'center', 24 | }, 25 | avatar: { 26 | margin: theme.spacing(1), 27 | backgroundColor: theme.palette.secondary.main, 28 | }, 29 | form: { 30 | width: '100%', // Fix IE 11 issue. 31 | marginTop: theme.spacing(1), 32 | }, 33 | submit: { 34 | margin: theme.spacing(3, 0, 2), 35 | }, 36 | })); 37 | 38 | export default function SignIn() { 39 | const history = useHistory(); 40 | const initialFormData = Object.freeze({ 41 | email: '', 42 | password: '', 43 | }); 44 | 45 | const [formData, updateFormData] = useState(initialFormData); 46 | 47 | const handleChange = (e) => { 48 | updateFormData({ 49 | ...formData, 50 | [e.target.name]: e.target.value.trim(), 51 | }); 52 | }; 53 | 54 | const handleSubmit = (e) => { 55 | e.preventDefault(); 56 | console.log(formData); 57 | 58 | axiosInstance 59 | .post(`token/`, { 60 | email: formData.email, 61 | password: formData.password, 62 | }) 63 | .then((res) => { 64 | localStorage.setItem('access_token', res.data.access); 65 | localStorage.setItem('refresh_token', res.data.refresh); 66 | axiosInstance.defaults.headers['Authorization'] = 67 | 'JWT ' + localStorage.getItem('access_token'); 68 | history.push('/'); 69 | //console.log(res); 70 | //console.log(res.data); 71 | }); 72 | }; 73 | 74 | const classes = useStyles(); 75 | 76 | return ( 77 | 78 | 79 |
80 | 81 | 82 | Sign in 83 | 84 |
85 | 97 | 109 | } 111 | label="Remember me" 112 | /> 113 | 123 | 124 | 125 | 126 | Forgot password? 127 | 128 | 129 | 130 | 131 | {"Don't have an account? Sign Up"} 132 | 133 | 134 | 135 | 136 |
137 |
138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /react/blogapi/src/components/auth/logout.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import axiosInstance from '../../axios'; 3 | import { useHistory } from 'react-router-dom'; 4 | 5 | export default function SignUp() { 6 | const history = useHistory(); 7 | 8 | useEffect(() => { 9 | const response = axiosInstance.post('user/logout/blacklist/', { 10 | refresh_token: localStorage.getItem('refresh_token'), 11 | }); 12 | localStorage.removeItem('access_token'); 13 | localStorage.removeItem('refresh_token'); 14 | axiosInstance.defaults.headers['Authorization'] = null; 15 | history.push('/login'); 16 | }); 17 | return
Logout
; 18 | } 19 | -------------------------------------------------------------------------------- /react/blogapi/src/components/auth/register.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import axiosInstance from '../../axios'; 3 | import { useHistory } from 'react-router-dom'; 4 | //MaterialUI 5 | import Avatar from '@material-ui/core/Avatar'; 6 | import Button from '@material-ui/core/Button'; 7 | import CssBaseline from '@material-ui/core/CssBaseline'; 8 | import TextField from '@material-ui/core/TextField'; 9 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 10 | import Checkbox from '@material-ui/core/Checkbox'; 11 | import Link from '@material-ui/core/Link'; 12 | import Grid from '@material-ui/core/Grid'; 13 | import Typography from '@material-ui/core/Typography'; 14 | import { makeStyles } from '@material-ui/core/styles'; 15 | import Container from '@material-ui/core/Container'; 16 | 17 | const useStyles = makeStyles((theme) => ({ 18 | paper: { 19 | marginTop: theme.spacing(8), 20 | display: 'flex', 21 | flexDirection: 'column', 22 | alignItems: 'center', 23 | }, 24 | avatar: { 25 | margin: theme.spacing(1), 26 | backgroundColor: theme.palette.secondary.main, 27 | }, 28 | form: { 29 | width: '100%', // Fix IE 11 issue. 30 | marginTop: theme.spacing(3), 31 | }, 32 | submit: { 33 | margin: theme.spacing(3, 0, 2), 34 | }, 35 | })); 36 | 37 | export default function SignUp() { 38 | const history = useHistory(); 39 | const initialFormData = Object.freeze({ 40 | email: '', 41 | username: '', 42 | password: '', 43 | }); 44 | 45 | const [formData, updateFormData] = useState(initialFormData); 46 | 47 | const handleChange = (e) => { 48 | updateFormData({ 49 | ...formData, 50 | // Trimming any whitespace 51 | [e.target.name]: e.target.value.trim(), 52 | }); 53 | }; 54 | 55 | const handleSubmit = (e) => { 56 | e.preventDefault(); 57 | console.log(formData); 58 | 59 | axiosInstance 60 | .post(`user/create/`, { 61 | email: formData.email, 62 | user_name: formData.username, 63 | password: formData.password, 64 | }) 65 | .then((res) => { 66 | history.push('/login'); 67 | console.log(res); 68 | console.log(res.data); 69 | }); 70 | }; 71 | 72 | const classes = useStyles(); 73 | 74 | return ( 75 | 76 | 77 |
78 | 79 | 80 | Sign up 81 | 82 |
83 | 84 | 85 | 95 | 96 | 97 | 107 | 108 | 109 | 120 | 121 | 122 | } 124 | label="I want to receive inspiration, marketing promotions and updates via email." 125 | /> 126 | 127 | 128 | 138 | 139 | 140 | 141 | Already have an account? Sign in 142 | 143 | 144 | 145 |
146 |
147 |
148 | ); 149 | } 150 | -------------------------------------------------------------------------------- /react/blogapi/src/components/footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Container from '@material-ui/core/Container'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import Grid from '@material-ui/core/Grid'; 6 | import Link from '@material-ui/core/Link'; 7 | import Box from '@material-ui/core/Box'; 8 | 9 | const useStyles = makeStyles((theme) => ({ 10 | '@global': { 11 | ul: { 12 | margin: 0, 13 | padding: 0, 14 | listStyle: 'none', 15 | }, 16 | }, 17 | footer: { 18 | borderTop: `1px solid ${theme.palette.divider}`, 19 | marginTop: theme.spacing(8), 20 | paddingTop: theme.spacing(3), 21 | paddingBottom: theme.spacing(3), 22 | [theme.breakpoints.up('sm')]: { 23 | paddingTop: theme.spacing(6), 24 | paddingBottom: theme.spacing(6), 25 | }, 26 | }, 27 | })); 28 | 29 | function Copyright() { 30 | return ( 31 | 32 | {'Copyright © '} 33 | 34 | Your Website 35 | {' '} 36 | {new Date().getFullYear()} 37 | {'.'} 38 | 39 | ); 40 | } 41 | 42 | const footers = [ 43 | { 44 | title: 'Company', 45 | description: ['Team', 'History', 'Contact us', 'Locations'], 46 | }, 47 | { 48 | title: 'Features', 49 | description: [ 50 | 'Cool stuff', 51 | 'Random feature', 52 | 'Team feature', 53 | 'Developer stuff', 54 | 'Another one', 55 | ], 56 | }, 57 | { 58 | title: 'Resources', 59 | description: [ 60 | 'Resource', 61 | 'Resource name', 62 | 'Another resource', 63 | 'Final resource', 64 | ], 65 | }, 66 | { 67 | title: 'Legal', 68 | description: ['Privacy policy', 'Terms of use'], 69 | }, 70 | ]; 71 | 72 | function Footer() { 73 | const classes = useStyles(); 74 | return ( 75 | 76 | 77 | 78 | {footers.map((footer) => ( 79 | 80 | 81 | {footer.title} 82 | 83 |
    84 | {footer.description.map((item) => ( 85 |
  • 86 | 87 | {item} 88 | 89 |
  • 90 | ))} 91 |
92 |
93 | ))} 94 |
95 | 96 | 97 | 98 |
99 |
100 | ); 101 | } 102 | 103 | export default Footer; 104 | -------------------------------------------------------------------------------- /react/blogapi/src/components/header.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import AppBar from '@material-ui/core/AppBar'; 3 | import Toolbar from '@material-ui/core/Toolbar'; 4 | import Typography from '@material-ui/core/Typography'; 5 | import CssBaseline from '@material-ui/core/CssBaseline'; 6 | import { makeStyles } from '@material-ui/core/styles'; 7 | import { NavLink } from 'react-router-dom'; 8 | import Link from '@material-ui/core/Link'; 9 | import Button from '@material-ui/core/Button'; 10 | import SearchBar from 'material-ui-search-bar'; 11 | import { useHistory } from 'react-router-dom'; 12 | 13 | const useStyles = makeStyles((theme) => ({ 14 | appBar: { 15 | borderBottom: `1px solid ${theme.palette.divider}`, 16 | }, 17 | link: { 18 | margin: theme.spacing(1, 1.5), 19 | }, 20 | toolbarTitle: { 21 | flexGrow: 1, 22 | }, 23 | })); 24 | 25 | function Header() { 26 | const classes = useStyles(); 27 | let history = useHistory(); 28 | const [data, setData] = useState({ search: '' }); 29 | 30 | const goSearch = (e) => { 31 | history.push({ 32 | pathname: '/search/', 33 | search: '?search=' + data.search, 34 | }); 35 | window.location.reload(); 36 | }; 37 | return ( 38 | 39 | 40 | 46 | 47 | 53 | 59 | Blog 60 | 61 | 62 | 63 | setData({ search: newValue })} 66 | onRequestSearch={() => goSearch(data.search)} 67 | /> 68 | 69 | 80 | 90 | 100 | 101 | 102 | 103 | ); 104 | } 105 | 106 | export default Header; 107 | -------------------------------------------------------------------------------- /react/blogapi/src/components/posts/postLoading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function PostLoading(Component) { 4 | return function PostLoadingComponent({ isLoading, ...props }) { 5 | if (!isLoading) return ; 6 | return ( 7 |

8 | We are waiting for the data to load!... 9 |

10 | ); 11 | }; 12 | } 13 | export default PostLoading; 14 | -------------------------------------------------------------------------------- /react/blogapi/src/components/posts/posts.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardContent from '@material-ui/core/CardContent'; 5 | import CardMedia from '@material-ui/core/CardMedia'; 6 | import Grid from '@material-ui/core/Grid'; 7 | import Typography from '@material-ui/core/Typography'; 8 | import Container from '@material-ui/core/Container'; 9 | import Link from '@material-ui/core/Link'; 10 | 11 | const useStyles = makeStyles((theme) => ({ 12 | cardMedia: { 13 | paddingTop: '56.25%', // 16:9 14 | }, 15 | link: { 16 | margin: theme.spacing(1, 1.5), 17 | }, 18 | cardHeader: { 19 | backgroundColor: 20 | theme.palette.type === 'light' 21 | ? theme.palette.grey[200] 22 | : theme.palette.grey[700], 23 | }, 24 | postTitle: { 25 | fontSize: '16px', 26 | textAlign: 'left', 27 | }, 28 | postText: { 29 | display: 'flex', 30 | justifyContent: 'left', 31 | alignItems: 'baseline', 32 | fontSize: '12px', 33 | textAlign: 'left', 34 | marginBottom: theme.spacing(2), 35 | }, 36 | })); 37 | 38 | const Posts = (props) => { 39 | const { posts } = props; 40 | const classes = useStyles(); 41 | if (!posts || posts.length === 0) return

Can not find any posts, sorry

; 42 | return ( 43 | 44 | 45 | 46 | {posts.map((post) => { 47 | return ( 48 | // Enterprise card is full width at sm breakpoint 49 | 50 | 51 | 56 | 61 | 62 | 63 | 69 | {post.title.substr(0, 50)}... 70 | 71 |
72 | 73 | {post.excerpt.substr(0, 40)}... 74 | 75 |
76 |
77 |
78 |
79 | ); 80 | })} 81 |
82 |
83 |
84 | ); 85 | }; 86 | export default Posts; 87 | -------------------------------------------------------------------------------- /react/blogapi/src/components/posts/search.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import axiosInstance from '../../axios'; 3 | import { makeStyles } from '@material-ui/core/styles'; 4 | import Card from '@material-ui/core/Card'; 5 | import CardContent from '@material-ui/core/CardContent'; 6 | import CardMedia from '@material-ui/core/CardMedia'; 7 | import Grid from '@material-ui/core/Grid'; 8 | import Typography from '@material-ui/core/Typography'; 9 | import Container from '@material-ui/core/Container'; 10 | import Link from '@material-ui/core/Link'; 11 | 12 | const useStyles = makeStyles((theme) => ({ 13 | cardMedia: { 14 | paddingTop: '56.25%', // 16:9 15 | }, 16 | link: { 17 | margin: theme.spacing(1, 1.5), 18 | }, 19 | cardHeader: { 20 | backgroundColor: 21 | theme.palette.type === 'light' 22 | ? theme.palette.grey[200] 23 | : theme.palette.grey[700], 24 | }, 25 | postTitle: { 26 | fontSize: '16px', 27 | textAlign: 'left', 28 | }, 29 | postText: { 30 | display: 'flex', 31 | justifyContent: 'left', 32 | alignItems: 'baseline', 33 | fontSize: '12px', 34 | textAlign: 'left', 35 | marginBottom: theme.spacing(2), 36 | }, 37 | })); 38 | 39 | const Search = () => { 40 | const classes = useStyles(); 41 | const search = 'search'; 42 | const [appState, setAppState] = useState({ 43 | search: '', 44 | posts: [], 45 | }); 46 | 47 | useEffect(() => { 48 | axiosInstance.get(search + '/' + window.location.search).then((res) => { 49 | const allPosts = res.data; 50 | setAppState({ posts: allPosts }); 51 | console.log(res.data); 52 | }); 53 | }, [setAppState]); 54 | 55 | return ( 56 | 57 | 58 | 59 | {appState.posts.map((post) => { 60 | return ( 61 | // Enterprise card is full width at sm breakpoint 62 | 63 | 64 | 69 | 74 | 75 | 76 | 82 | {post.title.substr(0, 50)}... 83 | 84 |
85 | 86 | {post.excerpt.substr(0, 40)}... 87 | 88 |
89 |
90 |
91 |
92 | ); 93 | })} 94 |
95 |
96 |
97 | ); 98 | }; 99 | export default Search; 100 | -------------------------------------------------------------------------------- /react/blogapi/src/components/posts/single.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import axiosInstance from '../../axios'; 3 | import { useParams } from 'react-router-dom'; 4 | //MaterialUI 5 | import CssBaseline from '@material-ui/core/CssBaseline'; 6 | import { makeStyles } from '@material-ui/core/styles'; 7 | import Container from '@material-ui/core/Container'; 8 | import Typography from '@material-ui/core/Typography'; 9 | 10 | const useStyles = makeStyles((theme) => ({ 11 | paper: { 12 | marginTop: theme.spacing(8), 13 | display: 'flex', 14 | flexDirection: 'column', 15 | alignItems: 'center', 16 | }, 17 | })); 18 | 19 | export default function Post() { 20 | const { slug } = useParams(); 21 | const classes = useStyles(); 22 | 23 | const [data, setData] = useState({ 24 | posts: [], 25 | }); 26 | 27 | useEffect(() => { 28 | axiosInstance.get('post/' + slug).then((res) => { 29 | setData({ 30 | posts: res.data, 31 | }); 32 | console.log(res.data); 33 | }); 34 | }, [setData]); 35 | 36 | return ( 37 | 38 | 39 |
{' '} 40 |
41 | 42 | 49 | {data.posts.title}{' '} 50 | {' '} 51 | 57 | {data.posts.excerpt}{' '} 58 | {' '} 59 | {' '} 60 |
{' '} 61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /react/blogapi/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /react/blogapi/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import * as serviceWorker from './serviceWorker'; 4 | import './index.css'; 5 | import { Route, BrowserRouter as Router, Switch } from 'react-router-dom'; 6 | import App from './App'; 7 | import Header from './components/header'; 8 | import Footer from './components/footer'; 9 | import Register from './components/auth/register'; 10 | import Login from './components/auth/login'; 11 | import Logout from './components/auth/logout'; 12 | import Single from './components/posts/single'; 13 | import Search from './components/posts/search'; 14 | import Admin from './Admin'; 15 | import Create from './components/admin/create'; 16 | import Edit from './components/admin/edit'; 17 | import Delete from './components/admin/delete'; 18 | 19 | const routing = ( 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |