diff --git a/server/core/settings.py b/server/core/settings.py index c95d707..c8f49d9 100644 --- a/server/core/settings.py +++ b/server/core/settings.py @@ -11,7 +11,7 @@ """ from pathlib import Path - +import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -42,7 +42,8 @@ 'ckeditor', 'ckeditor_uploader', ] -CKEDITOR_UPLOAD_PATH = "uploads/" +CKEDITOR_UPLOAD_PATH = "images/ckeditor_uploads/" + CKEDITOR_CONFIGS = { 'default': { 'toolbar': 'full', @@ -143,9 +144,12 @@ # https://docs.djangoproject.com/en/5.0/howto/static-files/ STATIC_URL = 'static/' +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') STATICFILES_DIRS = [ '../client/build/static/', + os.path.join(BASE_DIR, 'static'), ] # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field diff --git a/server/core/urls.py b/server/core/urls.py index f5e494b..d99d600 100644 --- a/server/core/urls.py +++ b/server/core/urls.py @@ -16,18 +16,14 @@ """ from django.contrib import admin from django.urls import path, include -#from django.views.generic import TemplateView -#from django.views.static import serve -from research.views import ArticleListCreate, ArticleDetail -from rest_framework.urlpatterns import format_suffix_patterns - +from django.conf import settings +from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('', include('research.urls')), path('api/', include('research.urls')), - #re_path(r'^(?!/?static/)(?!/?media/)(?P.*\..*)$', serve), - #re_path(r'^.*$', TemplateView.as_view(template_name='index.html')), ] -urlpatterns = format_suffix_patterns(urlpatterns) \ No newline at end of file +if settings.DEBUG: + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/server/media/images/0-simple_web_stack.jpg b/server/media/images/0-simple_web_stack.jpg new file mode 100644 index 0000000..eccb12f Binary files /dev/null and b/server/media/images/0-simple_web_stack.jpg differ diff --git a/server/media/images/2077-Collective.png b/server/media/images/2077-Collective.png new file mode 100644 index 0000000..6b317d0 Binary files /dev/null and b/server/media/images/2077-Collective.png differ diff --git a/server/media/images/2077-Collective_CmxVQmg.png b/server/media/images/2077-Collective_CmxVQmg.png new file mode 100644 index 0000000..6b317d0 Binary files /dev/null and b/server/media/images/2077-Collective_CmxVQmg.png differ diff --git a/server/media/images/2077-Collective_NKzY9zM.png b/server/media/images/2077-Collective_NKzY9zM.png new file mode 100644 index 0000000..6b317d0 Binary files /dev/null and b/server/media/images/2077-Collective_NKzY9zM.png differ diff --git a/server/media/images/2077-Collective_Vt5LMm4.png b/server/media/images/2077-Collective_Vt5LMm4.png new file mode 100644 index 0000000..6b317d0 Binary files /dev/null and b/server/media/images/2077-Collective_Vt5LMm4.png differ diff --git a/server/media/images/2077-Collective_sdam6F2.png b/server/media/images/2077-Collective_sdam6F2.png new file mode 100644 index 0000000..6b317d0 Binary files /dev/null and b/server/media/images/2077-Collective_sdam6F2.png differ diff --git a/server/media/images/2077-Collective_yYKWZf2.png b/server/media/images/2077-Collective_yYKWZf2.png new file mode 100644 index 0000000..6b317d0 Binary files /dev/null and b/server/media/images/2077-Collective_yYKWZf2.png differ diff --git a/server/media/images/mynft.png b/server/media/images/mynft.png new file mode 100644 index 0000000..d270697 Binary files /dev/null and b/server/media/images/mynft.png differ diff --git a/server/media/images/mynft_D0F3m8M.png b/server/media/images/mynft_D0F3m8M.png new file mode 100644 index 0000000..d270697 Binary files /dev/null and b/server/media/images/mynft_D0F3m8M.png differ diff --git a/server/media/images/mynft_bvdjr3c.png b/server/media/images/mynft_bvdjr3c.png new file mode 100644 index 0000000..d270697 Binary files /dev/null and b/server/media/images/mynft_bvdjr3c.png differ diff --git a/server/media/images/mynft_fXK8jpO.png b/server/media/images/mynft_fXK8jpO.png new file mode 100644 index 0000000..d270697 Binary files /dev/null and b/server/media/images/mynft_fXK8jpO.png differ diff --git a/server/research/migrations/0008_alter_article_thumb.py b/server/research/migrations/0008_alter_article_thumb.py new file mode 100644 index 0000000..f17bb46 --- /dev/null +++ b/server/research/migrations/0008_alter_article_thumb.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.7 on 2024-07-22 17:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research', '0007_alter_article_status'), + ] + + operations = [ + migrations.AlterField( + model_name='article', + name='thumb', + field=models.ImageField(blank=True, default='../static/images/img.webp', upload_to=''), + ), + ] diff --git a/server/research/migrations/0009_alter_article_thumb.py b/server/research/migrations/0009_alter_article_thumb.py new file mode 100644 index 0000000..480171c --- /dev/null +++ b/server/research/migrations/0009_alter_article_thumb.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.7 on 2024-07-22 18:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research', '0008_alter_article_thumb'), + ] + + operations = [ + migrations.AlterField( + model_name='article', + name='thumb', + field=models.ImageField(blank=True, default='../media/images/img.webp', upload_to='images/'), + ), + ] diff --git a/server/research/migrations/0010_alter_article_thumb.py b/server/research/migrations/0010_alter_article_thumb.py new file mode 100644 index 0000000..1a2a1a5 --- /dev/null +++ b/server/research/migrations/0010_alter_article_thumb.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.7 on 2024-07-22 18:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research', '0009_alter_article_thumb'), + ] + + operations = [ + migrations.AlterField( + model_name='article', + name='thumb', + field=models.ImageField(blank=True, default='/media/images/img.webp', upload_to='images/'), + ), + ] diff --git a/server/research/migrations/0011_alter_article_thumb.py b/server/research/migrations/0011_alter_article_thumb.py new file mode 100644 index 0000000..bbfef07 --- /dev/null +++ b/server/research/migrations/0011_alter_article_thumb.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.7 on 2024-07-22 18:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research', '0010_alter_article_thumb'), + ] + + operations = [ + migrations.AlterField( + model_name='article', + name='thumb', + field=models.ImageField(blank=True, default='images/img.webp', upload_to='images/'), + ), + ] diff --git a/server/research/migrations/0012_alter_article_thumb.py b/server/research/migrations/0012_alter_article_thumb.py new file mode 100644 index 0000000..2a87708 --- /dev/null +++ b/server/research/migrations/0012_alter_article_thumb.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.7 on 2024-07-22 18:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research', '0011_alter_article_thumb'), + ] + + operations = [ + migrations.AlterField( + model_name='article', + name='thumb', + field=models.ImageField(blank=True, default='../static/images/img.webp', upload_to='images/'), + ), + ] diff --git a/server/research/migrations/0013_alter_article_thumb.py b/server/research/migrations/0013_alter_article_thumb.py new file mode 100644 index 0000000..6f5104d --- /dev/null +++ b/server/research/migrations/0013_alter_article_thumb.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.7 on 2024-07-22 18:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('research', '0012_alter_article_thumb'), + ] + + operations = [ + migrations.AlterField( + model_name='article', + name='thumb', + field=models.ImageField(blank=True, default='../media/images/2077-Collective.png', upload_to='images/'), + ), + ] diff --git a/server/research/models.py b/server/research/models.py index d898894..da699c1 100644 --- a/server/research/models.py +++ b/server/research/models.py @@ -1,21 +1,18 @@ from django.db import models from django.contrib.auth.models import User from django.utils.text import slugify -from django.utils import timezone -# Create your models here. class Article(models.Model): - - # Custom manager to retrieve only ready articles + class ArticleObjects(models.Manager): def get_queryset(self): - return super().get_queryset() .filter(status='ready') + return super().get_queryset().filter(status='ready') options = ( ('draft', 'Draft'), ('ready', 'Ready'), ) - + title = models.CharField(max_length=100) content = models.TextField() summary = models.TextField(blank=True) @@ -23,30 +20,28 @@ def get_queryset(self): slug = models.SlugField(blank=True) created_at = models.DateTimeField(auto_now_add=True) category = models.CharField(max_length=100, blank=True) - thumb = models.ImageField(default='./static/images/img.webp', blank=True) + thumb = models.ImageField(upload_to='images/', default='../media/images/2077-Collective.png', blank=True) views = models.PositiveBigIntegerField(default=0) - status = models.CharField( - max_length=10, choices=options, default='draft') - objects = models.Manager() # default manager - postobjects = ArticleObjects() # custom manager + status = models.CharField(max_length=10, choices=options, default='draft') + objects = models.Manager() + postobjects = ArticleObjects() - # Order articles by creation date (most recent) class Meta: ordering = ('-created_at',) def __str__(self): return self.title - - # Generate a unique slug for each article + def save(self, *args, **kwargs): if not self.slug: - base_slug = slugify(self.title) - slug = base_slug - num = 1 - while Article.objects.filter(slug=slug).exists(): - slug = f"{base_slug}-{num}" - num += 1 - self.slug = slug + self.slug = self.generate_unique_slug() super().save(*args, **kwargs) - - \ No newline at end of file + + def generate_unique_slug(self): + base_slug = slugify(self.title) + slug = base_slug + num = 1 + while Article.objects.filter(slug=slug).exists(): + slug = f"{base_slug}-{num}" + num += 1 + return slug diff --git a/server/research/selectors.py b/server/research/selectors.py new file mode 100644 index 0000000..1c70658 --- /dev/null +++ b/server/research/selectors.py @@ -0,0 +1,7 @@ +from .models import Article + +def get_article_by_id(article_id): + return Article.objects.get(pk=article_id) + +def get_ready_articles(): + return Article.postobjects.all() diff --git a/server/research/services.py b/server/research/services.py new file mode 100644 index 0000000..12d4742 --- /dev/null +++ b/server/research/services.py @@ -0,0 +1,16 @@ +from .models import Article +from django.db.models import F + +def increment_article_views(article: Article): + """ + Increment the view count for the given article. + """ + # Increment the view count atomically + Article.objects.filter(pk=article.pk).update(views=F('views') + 1) + article.refresh_from_db() + +def create_article(**kwargs): + """ + Create a new article with the given parameters. + """ + return Article.objects.create(**kwargs) diff --git a/server/research/urls.py b/server/research/urls.py index 34b8050..b243e40 100644 --- a/server/research/urls.py +++ b/server/research/urls.py @@ -1,12 +1,12 @@ -from django.urls import path -from .views import index, ArticleListCreate, ArticleDetail, increment_view +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .views import ArticleViewSet, index +router = DefaultRouter() +router.register(r'articles', ArticleViewSet, basename='article') - -# Define the URL patterns for the research app urlpatterns = [ - path('', index, name='index'), - path('articles/', ArticleListCreate.as_view(), name='article-list-create'), - path('articles//', ArticleDetail.as_view(), name='article'), - path('articles//increment-views/', increment_view, name='increment_view'), # Increment views for an article - ] \ No newline at end of file + path('', index, name='index'), + path('api/', include(router.urls)), +] + diff --git a/server/research/views.py b/server/research/views.py index ff759e1..91ca5df 100644 --- a/server/research/views.py +++ b/server/research/views.py @@ -1,31 +1,23 @@ -from rest_framework import generics +from rest_framework import viewsets, status +from rest_framework.response import Response +from django.db.models import F from .models import Article from .serializers import ArticleSerializer -from django.http import JsonResponse -from django.shortcuts import render, get_object_or_404 -from django.views.decorators.http import require_POST -from django.views.decorators.csrf import csrf_exempt - - +from django.shortcuts import render def index(request): return render(request, 'index.html') - -# renders only the articles with status 'ready' -class ArticleListCreate(generics.ListCreateAPIView): - queryset = Article.postobjects.all() +class ArticleViewSet(viewsets.ModelViewSet): serializer_class = ArticleSerializer -class ArticleDetail(generics.RetrieveUpdateDestroyAPIView): - queryset = Article.objects.all() - serializer_class = ArticleSerializer - + def get_queryset(self): + return Article.objects.filter(status='ready') -# Increment views for an article -@csrf_exempt -@require_POST -def increment_view(request, pk): - article = get_object_or_404(Article, pk=pk) - article.views += 1 - article.save() - return JsonResponse({'views': article.views}) \ No newline at end of file + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + instance.views = F('views') + 1 + instance.save(update_fields=['views']) + # Refresh the instance to get the updated views value + instance.refresh_from_db(fields=['views']) + serializer = self.get_serializer(instance) + return Response(serializer.data) diff --git a/server/research/static/images/img.webp b/server/static/images/img.webp similarity index 100% rename from server/research/static/images/img.webp rename to server/static/images/img.webp diff --git a/server/research/static/style.css b/server/static/style.css similarity index 100% rename from server/research/static/style.css rename to server/static/style.css