diff --git a/.gitignore b/.gitignore index 437613e..7b279f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /server/.venv +/**/**/__pycache__ /client/node_modules /client/.cache diff --git a/.idea/misc.xml b/.idea/misc.xml index 4aaf26e..b30405f 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/.idea/the-freak-lesson.iml b/.idea/the-freak-lesson.iml index a9eafb1..59ea094 100644 --- a/.idea/the-freak-lesson.iml +++ b/.idea/the-freak-lesson.iml @@ -4,7 +4,7 @@ - + diff --git a/server/__init__.py b/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/blog/__init__.py b/server/blog/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/blog/admin.py b/server/blog/admin.py new file mode 100644 index 0000000..fee248f --- /dev/null +++ b/server/blog/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin +from .models import Tag, ArticleTags, Article, Author, Category, Comment + +# Register your models here. + +admin.site.register(Tag) +admin.site.register(ArticleTags) +admin.site.register(Article) +admin.site.register(Author) +admin.site.register(Category) +admin.site.register(Comment) diff --git a/server/blog/apps.py b/server/blog/apps.py new file mode 100644 index 0000000..94788a5 --- /dev/null +++ b/server/blog/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BlogConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'blog' diff --git a/server/blog/migrations/0001_initial.py b/server/blog/migrations/0001_initial.py new file mode 100644 index 0000000..f358a55 --- /dev/null +++ b/server/blog/migrations/0001_initial.py @@ -0,0 +1,76 @@ +# Generated by Django 4.2.7 on 2023-11-03 09:12 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Article', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('content', models.TextField()), + ('publication_date', models.DateField()), + ], + ), + migrations.CreateModel( + name='Author', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('email', models.EmailField(max_length=254)), + ('bio', models.TextField()), + ], + ), + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('author_name', models.CharField(max_length=100)), + ('email', models.EmailField(max_length=254)), + ('comment_text', models.TextField()), + ('comment_date', models.DateField()), + ('article_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article')), + ], + ), + migrations.CreateModel( + name='ArticleTags', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('article_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article')), + ('tag_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.tag')), + ], + ), + migrations.AddField( + model_name='article', + name='author_id', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.author'), + ), + migrations.AddField( + model_name='article', + name='category_id', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category'), + ), + ] diff --git a/server/blog/migrations/0002_rename_author_id_article_author_and_more.py b/server/blog/migrations/0002_rename_author_id_article_author_and_more.py new file mode 100644 index 0000000..f76298e --- /dev/null +++ b/server/blog/migrations/0002_rename_author_id_article_author_and_more.py @@ -0,0 +1,58 @@ +# Generated by Django 4.2.7 on 2023-11-03 09:27 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='article', + old_name='author_id', + new_name='author', + ), + migrations.RenameField( + model_name='article', + old_name='category_id', + new_name='category', + ), + migrations.RenameField( + model_name='article', + old_name='publication_date', + new_name='created_at', + ), + migrations.RenameField( + model_name='articletags', + old_name='article_id', + new_name='article', + ), + migrations.RenameField( + model_name='articletags', + old_name='tag_id', + new_name='tag', + ), + migrations.RenameField( + model_name='comment', + old_name='article_id', + new_name='article', + ), + migrations.RenameField( + model_name='comment', + old_name='author_name', + new_name='author', + ), + migrations.RenameField( + model_name='comment', + old_name='comment_date', + new_name='created_at', + ), + migrations.RenameField( + model_name='comment', + old_name='comment_text', + new_name='text', + ), + ] diff --git a/server/blog/migrations/__init__.py b/server/blog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/blog/models.py b/server/blog/models.py new file mode 100644 index 0000000..5953935 --- /dev/null +++ b/server/blog/models.py @@ -0,0 +1,51 @@ +from django.db import models + +# Create your models here. + +class Author(models.Model): + name = models.CharField(max_length=100) + email = models.EmailField() + bio = models.TextField() + + def __str__(self): + return self.name + +class Category(models.Model): + name = models.CharField(max_length=100) + + def __str__(self): + return self.name + + +class Article(models.Model): + title = models.CharField(max_length=200) + content = models.TextField() + created_at = models.DateField() + author = models.ForeignKey(Author, on_delete=models.CASCADE) + category = models.ForeignKey(Category, on_delete=models.CASCADE) + + def __str__(self): + return self.title + + +class Comment(models.Model): + article = models.ForeignKey(Article, on_delete=models.CASCADE) + author = models.CharField(max_length=100) # Who made the comment + email = models.EmailField() + text = models.TextField() + created_at = models.DateField() + + def __str__(self): + return self.comment_date + + +class Tag(models.Model): + name = models.CharField(max_length=255) + + def __str__(self): + return self.name + +# ArticleTags Table (a junction table to implement many-to-many relationship between Articles and Tags): +class ArticleTags(models.Model): + article = models.ForeignKey(Article, on_delete=models.CASCADE) + tag = models.ForeignKey(Tag, on_delete=models.CASCADE) diff --git a/server/blog/resolvers/__init__.py b/server/blog/resolvers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/blog/resolvers/mutations.py b/server/blog/resolvers/mutations.py new file mode 100644 index 0000000..7046399 --- /dev/null +++ b/server/blog/resolvers/mutations.py @@ -0,0 +1,57 @@ +import graphene +from graphene_django import DjangoObjectType +from ..models import Category, Article +from .queryTypes import CategoryType, ArticleType + + +class CategoryMutation(graphene.Mutation): + class Arguments: + name = graphene.String(required=True) + id = graphene.ID() + + category = graphene.Field(CategoryType) + + @classmethod + def mutate(cls, root, info, name, id=None): + if id: + category = Category.objects.get(pk=id) + category.name = name + category.save() + else: + category = Category.objects.create(name=name) + return CategoryMutation(category=category) + + +class ArticleMutation(graphene.Mutation): + class Arguments: + title = graphene.String(required=True) + content = graphene.String(required=True) + created_at = graphene.Date() + author_id = graphene.ID() + category_id = graphene.ID() + id = graphene.ID() + + article = graphene.Field(ArticleType) + + @classmethod + def mutate(cls, root, info, title, content, created_at, author_id, category_id, id=None): + if id: + article = Article.objects.get(pk=id) + article.title = title + article.content = content + if created_at: + article.created_at = created_at + if author_id: + article.author_id = author_id + if category_id: + article.category_id = category_id + article.save() + else: + article = Article.objects.create(title=title, content=content, created_at=created_at, author_id=author_id, + category_id=category_id) + return ArticleMutation(article=article) + + +class Mutation(graphene.ObjectType): + create_or_update_category = CategoryMutation.Field() + create_or_update_article = ArticleMutation.Field() diff --git a/server/blog/resolvers/queries.py b/server/blog/resolvers/queries.py new file mode 100644 index 0000000..78083cf --- /dev/null +++ b/server/blog/resolvers/queries.py @@ -0,0 +1,48 @@ +import graphene +from .queryTypes import AuthorType, CategoryType, ArticleType, CommentType, TagType +from ..models import Author, Category, Article, Comment, Tag + +class Query(graphene.ObjectType): + + all_authors = graphene.List(AuthorType) + all_categories = graphene.List(CategoryType) + all_articles = graphene.List(ArticleType, start=graphene.Int(), limit=graphene.Int()) + all_comments = graphene.List(CommentType) + all_tags = graphene.List(TagType) + + author_by_id = graphene.Field(AuthorType, id=graphene.Int()) + category_by_id = graphene.Field(CategoryType, id=graphene.Int()) + article_by_id = graphene.Field(ArticleType, id=graphene.Int()) + # comment_by_id = graphene.Field(CommentType, id=graphene.Int()) + # tag_by_id = graphene.Field(TagType, id=graphene.Int()) + + def resolve_all_authors(self, info): + return Author.objects.all() + + def resolve_all_categories(self, info): + return Category.objects.all() + + def resolve_all_articles(self, info, start: int=0, limit: int=20): + return Article.objects.all()[start:start+limit] + + def resolve_all_comments(self, info): + return Comment.objects.all() + + def resolve_all_tags(self, info): + return Tag.objects.all() + + def resolve_author_by_id(self, info, id): + return Author.objects.get(pk=id) + + def resolve_category_by_id(self, info, id): + return Category.objects.get(pk=id) + + def resolve_article_by_id(self, info, id): + return Article.objects.get(pk=id) + + # def resolve_comment_by_id(self, info, id): + # return Comment.objects.get(pk=id) + + # def resolve_tag_by_id(self, info, id): + # return Tag.objects.get(pk=id) + diff --git a/server/blog/resolvers/queryTypes.py b/server/blog/resolvers/queryTypes.py new file mode 100644 index 0000000..f6bb0cc --- /dev/null +++ b/server/blog/resolvers/queryTypes.py @@ -0,0 +1,22 @@ +from graphene_django.types import DjangoObjectType +from ..models import Author, Category, Article, Comment, Tag + +class AuthorType(DjangoObjectType): + class Meta: + model = Author + +class CategoryType(DjangoObjectType): + class Meta: + model = Category + +class ArticleType(DjangoObjectType): + class Meta: + model = Article + +class CommentType(DjangoObjectType): + class Meta: + model = Comment + +class TagType(DjangoObjectType): + class Meta: + model = Tag \ No newline at end of file diff --git a/server/blog/schema.py b/server/blog/schema.py new file mode 100644 index 0000000..ee88577 --- /dev/null +++ b/server/blog/schema.py @@ -0,0 +1,5 @@ +import graphene +from .resolvers.queries import Query +from .resolvers.mutations import Mutation + +schema = graphene.Schema(query=Query, mutation=Mutation) \ No newline at end of file diff --git a/server/blog/tests.py b/server/blog/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/blog/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/blog/urls.py b/server/blog/urls.py new file mode 100644 index 0000000..a57bcbe --- /dev/null +++ b/server/blog/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from graphene_django.views import GraphQLView + +urlpatterns = [ + # ... + path("graphql", GraphQLView.as_view(graphiql=True)), +] \ No newline at end of file diff --git a/server/blog/views.py b/server/blog/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/server/blog/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/server/core/__pycache__/__init__.cpython-310.pyc b/server/core/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index a985162..0000000 Binary files a/server/core/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/server/core/__pycache__/settings.cpython-310.pyc b/server/core/__pycache__/settings.cpython-310.pyc deleted file mode 100644 index 0b764b6..0000000 Binary files a/server/core/__pycache__/settings.cpython-310.pyc and /dev/null differ diff --git a/server/core/__pycache__/urls.cpython-310.pyc b/server/core/__pycache__/urls.cpython-310.pyc deleted file mode 100644 index c09a985..0000000 Binary files a/server/core/__pycache__/urls.cpython-310.pyc and /dev/null differ diff --git a/server/core/__pycache__/wsgi.cpython-310.pyc b/server/core/__pycache__/wsgi.cpython-310.pyc deleted file mode 100644 index 23df488..0000000 Binary files a/server/core/__pycache__/wsgi.cpython-310.pyc and /dev/null differ diff --git a/server/core/settings.py b/server/core/settings.py index 1abe9d8..5e44afb 100644 --- a/server/core/settings.py +++ b/server/core/settings.py @@ -37,8 +37,19 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + + #External + 'graphene_django', + + # Internal + 'blog', ] +# GraphQL Schema Path +GRAPHENE = { + "SCHEMA": "blog.schema.schema" +} + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/server/core/urls.py b/server/core/urls.py index 3b2f61e..0c83de6 100644 --- a/server/core/urls.py +++ b/server/core/urls.py @@ -15,8 +15,9 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), + path('', include('blog.urls')) ] diff --git a/server/db.sqlite3 b/server/db.sqlite3 index 681641c..110d67a 100644 Binary files a/server/db.sqlite3 and b/server/db.sqlite3 differ diff --git a/server/requirements.txt b/server/requirements.txt index c023ba7..e6e5002 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -1 +1,2 @@ -Django==4.2.7 \ No newline at end of file +Django==4.2.7 +graphene-django==3.1.5 \ No newline at end of file