Compare commits

...

28 Commits

Author SHA1 Message Date
325a234b18 Merge pull request 'add devel to master' (#4) from devel into master
Reviewed-on: LibertaCasa/webdev#4
2022-01-08 11:19:51 +01:00
add60af880 Merge pull request 'Integrating Frontend' (#3) from devel-frntnd into devel
Reviewed-on: LibertaCasa/webdev#3
2022-01-08 11:18:37 +01:00
0b64b4ed25
fix hugo theme conf 2022-01-08 15:45:26 +05:30
660fc54101
rename django project 2022-01-08 15:19:15 +05:30
06d4c3ee77
init work on hugo generated frntend 2022-01-08 15:16:05 +05:30
85217e4e9c
logging settings comment blocked in lieu of notif systems 2022-01-08 14:51:06 +05:30
79f6d10d9d
db settings 2022-01-08 14:44:43 +05:30
f3ecaedb55
restructure posts and fragment 2022-01-08 14:37:51 +05:30
47bbec8dfe
blog post init 2022-01-08 12:21:22 +05:30
b690967d5d
drfauth 2022-01-08 12:02:25 +05:30
2dae592258
snippetadmin init, logentries 2022-01-08 11:49:19 +05:30
c1371cdd94 Merge pull request 'imbibe master' (#2) from devel into master
Reviewed-on: LibertaCasa/webdev#2
2022-01-08 05:33:22 +01:00
7aecc21528 Merge pull request 'fix_snippets' (#1) from fix_snippets into devel
Reviewed-on: LibertaCasa/webdev#1
2022-01-08 05:31:38 +01:00
ccebb6f4a7
fix migrations 2022-01-08 09:57:26 +05:30
7922d749ab
fix snippets 2022-01-08 09:00:18 +05:30
a5cfb42818
set up viewsets with routers, note broken highlighting 2022-01-08 07:18:32 +05:30
91430d30a0
pagination 2022-01-08 06:58:16 +05:30
f0975c6505
hyperlinked relations 2022-01-08 06:56:58 +05:30
d2909e5ce9
highlight view 2022-01-08 06:49:47 +05:30
eb24f56b7c
object level permissions, basic auth flow achvd 2022-01-08 05:29:03 +05:30
17f5199fa1
routing for auth 2022-01-08 05:25:14 +05:30
18b063713e
permissions 2022-01-08 05:18:12 +05:30
3632f27583
user association 2 2022-01-08 05:13:06 +05:30
e8cd3c33d2
user association 1 2022-01-08 05:10:31 +05:30
a004b3eb0e
generic cbviews 2022-01-08 04:59:57 +05:30
37bf9a0c82
class based views 2022-01-08 04:58:32 +05:30
01035a2b45
Reqest Response 2022-01-08 04:54:32 +05:30
d414868d93
serializers 2022-01-08 04:42:34 +05:30
62 changed files with 572 additions and 33 deletions

8
.gitignore vendored
View File

@ -1,3 +1,5 @@
venv
.vscode
__pycache__
venv/
.vscode/
__pycache__/
db.sqlite3
.env

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "public/libcasa/themes/risotto"]
path = public/libcasa/themes/risotto
url = https://github.com/joeroe/risotto.git

View File

@ -0,0 +1,6 @@
# Django Backend for LibertaCasa
## Setup
* Use a virtual environment and `pip install -r requirements.txt`
* deps: `psychopg2`, `django`, `djangorestframework`.

View File

@ -1,3 +1,5 @@
from django.db import models
# Create your models here.

6
api/control/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ControlConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'control'

34
api/control/models.py Normal file
View File

@ -0,0 +1,34 @@
from django.db import models
class ScheduledTask(models.Model):
from control.tasks import REGISTERED_TASKS
class CatchupMode(models.IntegerChoices):
SKIP = 0, ('SKIP TO LATEST')
EXEC = 1, ('EXECUTE ALL')
class Interval(models.IntegerChoices):
HOURS = 1, ('HOURS')
DAYS = 24, ('DAYS')
WEEKS = 168, ('WEEKS')
active = models.BooleanField(default=False)
name = models.CharField(max_length=128)
action = models.CharField(max_length=128, choices=zip(
REGISTERED_TASKS.keys(), REGISTERED_TASKS.keys()))
kwargs = models.JSONField(blank=True, null=True)
repeat = models.IntegerField('Repeat Every', default=0)
repeat_interval = models.IntegerField(
'Interval', choices=Interval.choices, default=1)
start_datetime = models.DateTimeField()
end_datetime = models.DateTimeField(blank=True, null=True)
next_cycle = models.DateTimeField(blank=True, null=True)
catchup_mode = models.IntegerField(
default=0, choices=CatchupMode.choices)
class Meta():
verbose_name = 'Scheduled Task'
verbose_name_plural = 'Scheduled Tasks'
def __str__(self):
return self.name

9
api/control/tasks.py Normal file
View File

@ -0,0 +1,9 @@
from datetime import datetime, timezone, timedelta,
### TASKS ###
# def test_task(comms: dict, debug=False):
# timestamp = datetime.now(tz=timezone.utc).strftime("%b %d %Y %H:%M")
# text = "Test Task fired at " + timestamp
# blocks = [make_section(text)]
# process_comms(comms, blocks, debug)

5
api/posts/admin.py Normal file
View File

@ -0,0 +1,5 @@
from django.contrib import admin
from posts.models import *
admin.site.register(Post)

View File

@ -1,6 +1,6 @@
from django.apps import AppConfig
class BlogConfig(AppConfig):
class PostsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'blog'
name = 'posts'

View File

@ -0,0 +1,29 @@
# Generated by Django 4.0 on 2022-01-08 06:45
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='Post',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('title', models.CharField(blank=True, default='', max_length=100)),
('body', models.TextField(blank=True, default='')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='posts', to='auth.user')),
],
options={
'ordering': ['created'],
},
),
]

View File

18
api/posts/models.py Normal file
View File

@ -0,0 +1,18 @@
from django.db import models
# move to `const.py`
STATUS = (
(0,"Draft"),
(1,"Publish")
)
# Create your models here.
class Post(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
body = models.TextField(blank=True, default='')
owner = models.ForeignKey('auth.User', related_name='posts', on_delete=models.CASCADE)
class Meta:
ordering = ['created']

3
api/posts/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

11
api/posts/urls.py Normal file
View File

@ -0,0 +1,11 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'posts', views.PostsViewSet)
# router.register(r'users', views.UserViewSet)
urlpatterns = [
path('', include(router.urls)),
]

30
api/posts/views.py Normal file
View File

@ -0,0 +1,30 @@
from django.contrib.auth.models import User
from django.shortcuts import render
from snippets.serializers import UserSerializer
from .models import Post
from .serializers import PostSerializer
from posts.permissions import IsOwnerOrReadOnly
from rest_framework import permissions, renderers
from rest_framework.reverse import reverse
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
class PostsViewSet(viewsets.ModelViewSet):
"""
docstring
"""
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `retrieve` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer

0
api/snippets/__init__.py Normal file
View File

80
api/snippets/admin.py Normal file
View File

@ -0,0 +1,80 @@
from django.contrib import admin
from django.contrib.admin.models import LogEntry, DELETION
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.urls import reverse
from snippets.models import *
#LogEntry
@admin.register(LogEntry)
class LogEntryAdmin(admin.ModelAdmin):
# to have a date-based drilldown navigation in the admin page
date_hierarchy = "action_time"
# to filter the resultes by users, content types and action flags
list_filter = ["user", "content_type", "action_flag"]
# when searching the user will be able to search in both object_repr and change_message
search_fields = ["object_repr", "change_message"]
list_display = [
"action_time",
"user",
"content_type",
"action_flag",
"object_link",
]
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
def has_view_permission(self, request, obj=None):
return request.user.is_staff
def object_link(self, obj):
if obj.action_flag == DELETION:
link = escape(obj.object_repr)
else:
ct = obj.content_type
link = '<a href="%s">%s</a>' % (
reverse(
"admin:%s_%s_change" % (ct.app_label, ct.model),
args=[obj.object_id],
),
escape(obj.object_repr),
)
return mark_safe(link)
object_link.admin_order_field = "object_repr"
object_link.short_description = "object"
@admin.register(Snippet)
class SnippetAdmin(admin.ModelAdmin):
date_hierarchy = "created"
ordering= ['-created']
# add hyperlinked highlighted view and
# possibly code preview js magic
list_filter = ('owner', 'language', 'style')
list_display = ['id',
'title',
'owner',
'created',
'language']
search_fields = ['owner', 'title', 'language']
# @admin.register)
# class SnippetAdmin(admin.ModelAdmin):
# pass

6
api/snippets/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class SnippetsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'snippets'

File diff suppressed because one or more lines are too long

View File

39
api/snippets/models.py Normal file
View File

@ -0,0 +1,39 @@
from django.db import models
# pygments
from pygments import highlight
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
# modified?
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
owner = models.ForeignKey('auth.user', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
class Meta:
ordering = ['created']
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = 'table' if self.linenos else False
options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super().save(*args, **kwargs)

View File

@ -0,0 +1,15 @@
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user

View File

@ -0,0 +1,18 @@
from django.contrib.auth.models import User
from rest_framework import serializers
from .models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ['url', 'id', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style']
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ['url', 'id', 'username', 'snippets']

3
api/snippets/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

12
api/snippets/urls.py Normal file
View File

@ -0,0 +1,12 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)
urlpatterns = [
path('', include(router.urls)),
]

37
api/snippets/views.py Normal file
View File

@ -0,0 +1,37 @@
from django.contrib.auth.models import User
from .models import Snippet
from .serializers import SnippetSerializer, UserSerializer
from snippets.permissions import IsOwnerOrReadOnly
from rest_framework import permissions, renderers
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
class SnippetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
Additionally we also provide an extra `highlight` action.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly]
@action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `retrieve` actions.
"""
queryset = User.objects.all()
serializer_class = UserSerializer

0
api/website/__init__.py Normal file
View File

View File

@ -11,10 +11,26 @@ https://docs.djangoproject.com/en/4.0/ref/settings/
"""
from pathlib import Path
import os
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
## ENVARS ##
POSTGRES_DB = os.getenv('POSTGRES_DB')
POSTGRES_USER = os.getenv('POSTGRES_USER')
POSTGRES_PASSWORD = os.getenv('POSTGRES_PASSWORD')
POSTGRES_HOST = os.getenv('POSTGRES_HOST')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
if os.getenv('DEBUG') == 'True':
DEBUG = True
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
@ -37,6 +53,11 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'snippets.apps.SnippetsConfig',
'posts.apps.PostsConfig',
'api.apps.ApiConfig',
'control.apps.ControlConfig',
]
MIDDLEWARE = [
@ -75,11 +96,23 @@ WSGI_APPLICATION = 'website.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': POSTGRES_DB,
'USER': POSTGRES_USER,
'PASSWORD': POSTGRES_PASSWORD,
'HOST': POSTGRES_HOST,
'PORT': '5432',
}
}
if os.getenv('DEV') == 'true':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
@ -121,3 +154,35 @@ STATIC_URL = 'static/'
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
}
# LOGGING = {
# 'version': 1,
# 'disable_existing_loggers': False,
# 'handlers': {
# 'file': {
# 'level': 'DEBUG',
# 'class': 'logging.FileHandler',
# 'filename': './debug.log',
# },
# 'error_file': {
# 'level': 'ERROR',
# 'class': 'logging.FileHandler',
# 'filename': './error.log',
# },
# },
# 'loggers': {
# 'django': {
# 'handlers': ['file', 'error_file'],
# 'propagate': True,
# },
# },
# }

View File

@ -14,8 +14,14 @@ Including another URLconf
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('snippets/', include('snippets.urls')),
path('posts/', include('posts.urls'))
]
urlpatterns += [
path('api-auth/', include('rest_framework.urls')),
]

View File

View File

@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

53
public/libcasa/config.yml Normal file
View File

@ -0,0 +1,53 @@
baseURL: https://liberta.casa
theme: risotto
languageCode: en-us
title: Liberta Casa
paginate: 3
authors: [ Georg Pfuetzenreuter, Pratyush Desai ]
# Automatically add content sections to main menu
sectionPagesMenu: main
params:
theme:
palette: monokai-dark
mode: dark-mode
about:
title: Liberta Casa
description: 'For those who FLOSS shall be free.'
logo: images/logo.png
socialLinks:
- icon: fab fa-irc
title: IRC
url: 'https://liberta.casa/gamja'
- icon: fas fa-envelope
title: Email
url: 'mailto:hello@liberta.casa'
menu:
main:
- identifier: about
name: About
url: /about/
weight: 10
- identifier: rules
name: Rules
url: /rules/
weight: 10
- identifier: faqs
name: FAQ
url: /faqs/
weight: 10
- identifier: accounts
name: Account
url: /accounts/
weight: 10
- identifier: tools
name: Tools
url: /tools/
weight: 10
taxonomies:
category: categories
tag: tags
series: series

View File

@ -0,0 +1,3 @@
+++
author = "LibCasa Authors"
+++

View File

@ -0,0 +1,7 @@
+++
title = "About"
description = "Liberta Casa, providing services.. for some reason."
date = "2021-12-12"
aliases = ["about-us", "about-libertacasa", "contact"]
author = "LibCasa Authors"
+++

View File

@ -0,0 +1,6 @@
---
title: "Accounts"
date: 2022-01-08T15:14:39+05:30
draft: true
---

View File

@ -0,0 +1,6 @@
---
title: "Faqs"
date: 2022-01-08T15:14:13+05:30
draft: true
---

View File

@ -0,0 +1,3 @@
+++
title = "Rules"
+++

View File

@ -0,0 +1,6 @@
---
title: "Tools"
date: 2022-01-08T15:14:47+05:30
draft: true
---

@ -0,0 +1 @@
Subproject commit 07f1b3ecfd4202a69847d47c89ece4e4d01278c4

View File

@ -2,5 +2,6 @@ asgiref==3.4.1
Django==4.0
djangorestframework==3.13.1
psycopg2==2.9.2
Pygments==2.11.2
pytz==2021.3
sqlparse==0.4.2

View File

@ -1,18 +0,0 @@
from django.db import models
# move to `const.py`
STATUS = (
(0,"Draft"),
(1,"Publish")
)
# Create your models here.
class Post(models.Model):
title = models.CharField(max_length=200, unique=True)
author = models.ForeignKey('user.auth',on_delete=models.CASCADE)
updated_on =models.DateTimeField(auto_now= True)
created_on = models.DateTimeField(auto_now_add=True)
content = models.TextField()
# status = models.IntegerChoices()

View File

@ -1,7 +0,0 @@
from django.urls import path, include
from . import views
urlpatterns = [
path('blogs/', include('blog.urls')),
path('', views.index, name='index')
]