0

シンプルな Geocache アプリケーションを作成しようとしています。バックエンド アクセスは次のように機能する必要があります。

  • 1 つの Geocache オブジェクトには、一般的な情報(作成日や難易度など)だけでなく、順序が固定されたいくつかの指示も含まれています(指示の例: 最初に経度/緯度を調整します)

  • 一般的な URL 構造は次のとおりです。

    • example.com/geocache/ジオキャッシュのリスト ビュー(取得)
    • example.com/geocache/<geocache_pk>/ジオキャッシュの詳細ビュー(get/post/put/delete) (すべての指示はここにまとめて表示されますが、ここでは操作できません)
    • example.com/geocache/<geocache_pk>/instruction/新しい指示を作成するためだけに(投稿)
    • example.com/geocache/<geocache_pk>/instruction/<instruction_position/>命令の操作/削除(put/delete)のみ

で正規表現を使用したカスタム アクションを通じてこの構造を達成しようとしましたurl_pathが、DRY が十分ではないと感じています。私は Django を数日間しか学んでいないので、いくつかの洗練されたパターンが欠けている可能性があります。

また、一般的なアプローチがあなたにとって意味があるかどうかも教えてください。

あなたの努力に感謝!改善するための提案があれば、本当に感謝しています。

models.py

from django.db import models
from django.db.models import F
from django.db.models.signals import pre_save, post_delete
from django.dispatch import receiver
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _


class Geocache(models.Model):
    class DifficultyLevel(models.TextChoices):
        BEGINNER = 1, _('Beginner')
        INTERMEDIATE = 2, _('Intermediate')
        ADVANCED = 3, _('Advanced')

    user_created = models.ForeignKey(User, related_name='geocaches_created', on_delete=models.CASCADE)
    date_created = models.DateTimeField(auto_now_add=True)
    user_last_modified = models.ForeignKey(User, related_name='geocaches_modified', on_delete=models.CASCADE)
    date_last_modified = models.DateTimeField(auto_now=True)
    title = models.CharField(max_length=70)
    difficulty_level = models.IntegerField(choices=DifficultyLevel.choices)

    def __str__(self):
        return self.title


class GeocacheInstruction(models.Model):
    class Meta:
        ordering = ['geocache', 'position']

    geocache = models.ForeignKey(Geocache, related_name='instructions', on_delete=models.CASCADE)
    position = models.IntegerField()
    loc_lon = models.DecimalField(max_digits=9, decimal_places=6)
    loc_lat = models.DecimalField(max_digits=9, decimal_places=6)
    title = models.CharField(max_length=70)
    instruction = models.TextField()

    def __str__(self):
        return self.title

    def is_saved(self):
        return self.id is not None


@receiver(pre_save, sender=GeocacheInstruction)
def rearrange_geocache_instruction_positions_pre_save(sender, instance, *args, **kwargs):
    """
    rearranges all positions before a new instruction gets inserted to maintain
    a sequential ordering of this field
    """

    # updating objects should not cause a reordering
    if instance.is_saved():
        return

    geocaches = instance.geocache.instructions.filter(position__gte=instance.position)
    geocaches.update(position=F('position')+1)


@receiver(post_delete, sender=GeocacheInstruction)
def rearrange_geocache_instruction_positions_post_delete(sender, instance, *args, **kwargs):
    """
    rearranges all positions after an instruction was deleted to maintain
    a sequential ordering of this field
    """
    geocaches = instance.geocache.instructions.filter(position__gt=instance.position)
    geocaches.update(position=F('position')-1)

serializers.py

from rest_framework import serializers
from geocaches.models import Geocache, GeocacheInstruction
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _


class GeocacheInstructionSerializer(serializers.ModelSerializer):
    class Meta:
        model = GeocacheInstruction
        fields = ['position', 'loc_lon', 'loc_lat', 'title', 'instruction']

    def create(self, validated_data):
        geocache = self.context.get('geocache')
        GeocacheInstruction.objects.create(geocache=geocache, **validated_data)
        return self

    def validate(self, data):
        """
        there should always be a sequential positioning therefore a new position
        is only allowed in the range from 0 to [highest_position] + 1
        """
        geocache = self.context.get('geocache')
        upper_bound = geocache.instructions.count() + 1

        if not (1 <= data['position'] <= upper_bound):
            raise ValidationError(
                _('The position %(position)s is not in the range from 1 - %(upper_bound)s.'),
                params={'position': data['position'], 'upper_bound': upper_bound}
            )

        return data


class GeocacheListSerializer(serializers.ModelSerializer):
    class Meta:
        model = Geocache
        fields = ['id', 'title', 'difficulty_level']


class GeocacheDetailSerializer(serializers.ModelSerializer):
    user_created = serializers.ReadOnlyField(source='user_created.username')
    user_last_modified = serializers.ReadOnlyField(source='user_last_modified.username')
    instructions = GeocacheInstructionSerializer(many=True, read_only=True)

    class Meta:
        model = Geocache
        fields = ['user_created', 'date_created', 'user_last_modified', 'date_last_modified', 'title',
                  'difficulty_level', 'instructions']

ビュー.py

from rest_framework import viewsets, permissions, mixins, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django.shortcuts import get_object_or_404
from geocaches.serializers import GeocacheDetailSerializer, GeocacheListSerializer, GeocacheInstructionSerializer
from geocaches.models import Geocache, GeocacheInstruction


class GeocacheViewSet(viewsets.ModelViewSet):

    serializer_class = GeocacheDetailSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def get_serializer_class(self):
        if self.action == 'list':
            return GeocacheListSerializer
        elif self.action == 'instruction_list':
            return GeocacheInstructionSerializer
        elif self.action == 'instruction_detail':
            return GeocacheInstructionSerializer
        else:
            return GeocacheDetailSerializer

    def get_queryset(self):
        if self.action == 'list':
            # Todo geodjango
            pass
        return Geocache.objects.all()

    @action(detail=True, url_path='instruction', url_name='instruction-list', methods=['post', 'get'])
    def instruction_list(self, request, pk):
        geocache = self.get_object()
        instructions = geocache.instructions.all()

        if request.method == 'GET':
            serializer = GeocacheInstructionSerializer(instructions, many=True)
            return Response(serializer.data)

        elif request.method == 'POST':
            serializer = GeocacheInstructionSerializer(data=request.data, context={'geocache': geocache})

            if serializer.is_valid():
                serializer.save()
                return Response({'status': 'Instruction created'}, status=status.HTTP_201_CREATED)
            else:
                return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    @action(detail=True,  url_name='instruction-detail', url_path='instruction/(?P<position>[^/.]+)',
            methods=['get', 'put', 'delete'])
    def instruction_detail(self, request, pk, position):
        geocache = self.get_object()
        instruction = get_object_or_404(geocache.instructions, position=position)

        if request.method == 'GET':
            serializer = GeocacheInstructionSerializer(instruction)
            return Response(serializer.data)

        elif request.method == 'PUT':
            serializer = GeocacheInstructionSerializer(instruction, data=request.data, context={'geocache': geocache})

            if serializer.is_valid():
                serializer.save()
                return Response({'status': 'Instruction altered'}, status=status.HTTP_200_OK)
            else:
                return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

        elif request.method == 'DELETE':
            instruction.delete()
            return Response({'status': 'Instruction deleted'}, status=status.HTTP_204_NO_CONTENT)

    def perform_create(self, serializer):
        serializer.save(user_created=self.request.user, user_last_modified=self.request.user)

    def perform_update(self, serializer):
        serializer.save(user_last_modified=self.request.user)

urls.py

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from geocaches import views

app_name = 'geocaches'

router = DefaultRouter()
router.register(r'geocache', views.GeocacheViewSet, basename='geocache')

urlpatterns = [
    path('', include(router.urls)),
]

urlpatterns += [
    path('api-auth/', include('rest_framework.urls')),
]
4

0 に答える 0