added first public views - no check for access rights at the moment

This commit is contained in:
Holger Sielaff
2024-07-12 17:22:17 +02:00
parent 7d53df6ea5
commit 3640ab759d
22 changed files with 386 additions and 69 deletions

View File

@@ -1,9 +1,14 @@
from django.contrib import admin
from django.utils.safestring import mark_safe
from content.models import Link, MediaFile, Question, QuestionVersion, Level, Label
from lib.utils import color_label
from content.models import Link, MediaFile, Question, QuestionVersion, Level, Label, SharedQuestion
from lib.mixins import PermissionsAdminMixin
from lib.utils import color_label
@admin.register(SharedQuestion)
class SharedQuestionAdmin(admin.ModelAdmin):
search_fields = ('user__username',)
@admin.register(Level)
@@ -22,15 +27,19 @@ class LabelAdmin(PermissionsAdminMixin, admin.ModelAdmin):
@admin.register(Question)
class QuestionAdmin(PermissionsAdminMixin, admin.ModelAdmin):
autocomplete_fields = ('medias', 'links', 'labels',)
autocomplete_fields = ('medias', 'links', 'labels', 'shares')
list_display = ('name', 'list_labels', 'list_level', 'author')
search_fields = ('name', 'question', 'awnser', 'description', 'label__name', 'level__value', 'level__name',)
def list_labels(self, instance):
return mark_safe(', '.join([color_label(l, value=l.name) for l in instance.labels.all()]))
if instance.labels:
return mark_safe(', '.join([color_label(l, value=l.name) for l in instance.labels.all()]))
return ''
def list_level(self, instance):
return mark_safe(color_label(instance.level, value=str(instance.level)))
if instance.level:
return mark_safe(color_label(instance.level, value=str(instance.level)))
return ''
list_level.short_description = 'Level'
list_labels.short_description = 'Labels'

View File

@@ -10,18 +10,14 @@ from django.forms.models import model_to_dict
from django.utils.safestring import mark_safe
from filer.fields.file import FilerFileField
from lib import get_current_user
from lib.core.db.models.base import SharedPermissionBase
from lib.core.db.models.mixins import DateAware, AuthorAware, DescriptionAware, NameAware
class MediaFile(models.Model):
name = models.CharField(max_length=25, unique=True, db_index=True)
class MediaFile(NameAware, DateAware, AuthorAware, DescriptionAware):
# https://django-filer.readthedocs.io/en/latest/extending_filer.html
# https://pypi.org/project/django-thumbnails/
file = FilerFileField(on_delete=models.RESTRICT, related_name='media_file')
description = models.TextField(null=True, blank=True)
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, default=get_current_user)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
@@ -30,13 +26,8 @@ class MediaFile(models.Model):
return self.file.name
class Link(models.Model):
name = models.CharField(max_length=25, null=True, blank=True, db_index=True)
class Link(NameAware, DateAware, AuthorAware, DescriptionAware):
url = models.URLField(unique=True, db_index=True)
description = models.TextField(null=True, blank=True)
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, default=get_current_user)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.url
@@ -45,14 +36,9 @@ class Link(models.Model):
return self.url
class Level(models.Model):
name = models.CharField(max_length=25, unique=True, db_index=True)
class Level(NameAware, DateAware, AuthorAware, DescriptionAware):
value = models.IntegerField(unique=True, db_index=True)
description = models.TextField(null=True, blank=True)
color = ColorField(default='#F90F90')
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, default=get_current_user)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.value} - {self.name}'
@@ -61,13 +47,8 @@ class Level(models.Model):
return f'{self.value}'
class Label(models.Model):
name = models.CharField(max_length=25, unique=True, db_index=True)
description = models.TextField(null=True, blank=True)
class Label(NameAware, DateAware, AuthorAware, DescriptionAware):
color = ColorField(default='#666666')
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, default=get_current_user)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
@@ -76,19 +57,21 @@ class Label(models.Model):
return self.name
class Question(models.Model):
class SharedQuestion(SharedPermissionBase):
class Meta:
abstract = False
class Question(DateAware, AuthorAware, DescriptionAware):
name = models.CharField(max_length=500, unique=True, db_index=True)
question = models.TextField(db_index=True)
buzzword = models.CharField(max_length=25, null=True, blank=True)
awnser = models.TextField(db_index=True)
level = models.ForeignKey(Level, on_delete=models.SET_NULL, null=True)
description = models.TextField(null=True, blank=True)
labels = models.ManyToManyField(Label, blank=True)
medias = models.ManyToManyField(MediaFile, blank=True)
links = models.ManyToManyField(Link, blank=True)
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
shares = models.ManyToManyField(SharedQuestion, blank=True)
def __str__(self):
return self.name
@@ -96,23 +79,38 @@ class Question(models.Model):
def __repr__(self):
return self.name
def to_view(self, for_players: bool = False):
ret = {}
ret['name'] = '{}{}'.format(self.name, f'({self.buzzword})' if self.buzzword else '')
ret['question'] = self.question
if self.description:
ret['question'] += ' \n\n------------------------------\n\n' + self.description
ret['awnser'] = self.awnser
ret['level'] = self.level.value
ret['medias'] = []
ret['links'] = []
for media in self.medias.all():
ret.setdefault('medias', []).append(os.path.basename(media.file.path))
for link in self.links.all():
ret.setdefault('links', []).append(link.url)
ret['question'] = mark_safe(ret['question'].replace('\n', '\n<br />'))
logging.error(ret)
return ret
@staticmethod
def searchdomain(term, from_quiz=False):
return \
models.Q(name__icontains=term) \
| models.Q(question__icontains=term) \
| models.Q(awnser__icontains=term) \
| models.Q(buzzword__icontains=term) \
| models.Q(labels__name__icontains=term)
@staticmethod
def get_by_tearchterm(term):
return Question.objects.filter(Question.searchdomain(term)).annotate(cnt=models.Count('id'))
def to_view(self, for_players: bool = False):
ret = {}
ret['name'] = '{}{}'.format(self.name, f'({self.buzzword})' if self.buzzword else '')
ret['question'] = self.question
if self.description:
ret['question'] += ' \n\n------------------------------\n\n' + self.description
ret['awnser'] = self.awnser
ret['level'] = self.level.value
ret['medias'] = []
ret['links'] = []
for media in self.medias.all():
ret.setdefault('medias', []).append(os.path.basename(media.file.path))
for link in self.links.all():
ret.setdefault('links', []).append(link.url)
ret['question'] = mark_safe(ret['question'].replace('\n', '\n<br />'))
logging.error(ret)
return ret
def default_json(o):
@@ -121,12 +119,9 @@ def default_json(o):
return json.dumps(model_to_dict(o))
class QuestionVersion(models.Model):
class QuestionVersion(DateAware, AuthorAware):
question = models.ForeignKey(Question, on_delete=models.SET_NULL, null=True)
data = models.JSONField()
author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.question} - {self.created_at}' if self.question else f'{self.data.name} - {self.created_at}'
@@ -140,6 +135,7 @@ def versionize_question(sender, instance: Question, *args, **kwargs):
data['medias'] = [{'id': m.id, '__str__': str(m)} for m in data['medias']]
data['links'] = [{'id': m.id, '__str__': str(m)} for m in data['links']]
data['labels'] = [{'id': m.id, '__str__': str(m)} for m in data['labels']]
data['shares'] = list(map(str, data['shares']))
QuestionVersion.objects.create(
question=instance,

29
content/serializers.py Normal file
View File

@@ -0,0 +1,29 @@
import logging
from rest_framework import serializers
from content.models import Question, Label, Level
class QuestionSerializer(serializers.Serializer):
# question = serializers.CharField()
# awnser = serializers.CharField()
# buzzword = serializers.CharField(required=False)
# level = serializers.IntegerField()
# labels = serializers.MultipleChoiceField(choices=[(l.id, l.name) for l in Label.objects.all()], required=False)
class Meta:
model = Question
# fields = ['id', 'name', 'description', 'level', 'labels', 'medias', ]
fields = '__all__'
"""
def create(self, validated_data):
return Question.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.name = validated_data.get('name', instance.name)
# ...
instance.save()
return instance
"""

View File

@@ -0,0 +1,43 @@
{% extends 'base.html' %}
{% block content %}
<form action="/questions" method="get" id="search_q_form">
<div id="question-search-field" class="row mb-5 mt-5">
<div class="col-12">
<div class="form-group">
<div class="input-group">
{% csrf_token %}
<input type="text" name="term" class="form-control p-2 bg-light" placeholder="Search for ..." value="{{ request.GET.term }}" />
<span class="input-group-text">
<i class="fa fa-search" onclick="document.getElementById('search_q_form').submit();"></i>
</span>
</div>
</div>
</div>
</div>
</form>
<div id="question-list" class="mt-2">
{% if items %}
<div class="row text-capitalize text-light bg-secondary border rounded p-2">
<div class="col-1">#</div>
<div class="col-3">Name</div>
<div class="col-3">Question</div>
<div class="col-1">Files</div>
<div class="col-1">Labels</div>
</div>
{% for q in items %}
<div class="row p-2">
<div class="col-1">{{ q.id }}</div>
<div class="col-3">{{ q.name }}</div>
<div class="col-3">{{ q.question }}</div>
<div class="col-1">{{ q.medias.all | length }}</div>
<div class="col-3">
{% for l in q.labels.all %}
{{ l.name }}
{% if not forloop.last %},{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
{% endif %}
</div>
{% endblock %}

6
content/urls.py Normal file
View File

@@ -0,0 +1,6 @@
from django.urls.conf import path
from content.views import public
urlpatterns = [
path('questions/', public.search_question, name='search-questions'),
]

View File

@@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -0,0 +1 @@
from .api import QuestionViewSet

50
content/views/api.py Normal file
View File

@@ -0,0 +1,50 @@
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema, status
from rest_framework import viewsets
from content.models import Question
from content.serializers import QuestionSerializer
from lib.api.permissions import IsAuthorOrReadOnly
class QuestionViewSet(viewsets.ModelViewSet):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
permission_classes = (
IsAuthorOrReadOnly,
)
@swagger_auto_schema(
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"question": openapi.Schema(
type=openapi.TYPE_STRING, description="Email Address"
),
"awnser": openapi.Schema(
type=openapi.TYPE_STRING, description="Previous Password"
),
"label": openapi.Schema(
type=openapi.TYPE_STRING, description="New Password"
),
},
),
responses={
status.HTTP_202_ACCEPTED: openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"status": openapi.Schema(type=openapi.TYPE_NUMBER),
"message": openapi.Schema(type=openapi.TYPE_STRING),
},
),
status.HTTP_401_UNAUTHORIZED: openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"status": openapi.Schema(type=openapi.TYPE_NUMBER),
"message": openapi.Schema(type=openapi.TYPE_STRING),
},
),
},
)
def create(self, request):
pass

13
content/views/public.py Normal file
View File

@@ -0,0 +1,13 @@
from django.shortcuts import render
from content.models import Question
def search_question(request):
term = request.GET.get('term')
items = []
if term:
items = Question.get_by_tearchterm(term)
else:
items = Question.objects.all()[:10]
return render(request, 'questions.html', {'items': items})