0
\$\begingroup\$

This app collects info via form that is rendered by Django. The code posts data with JavaScript to viewset. Anybody can post to viewset but only admin users can view or edit them.

Please review my code and point out bad practices or errors.

views

"""
I've used serializers with inputs overrides as forms here 
We can do this in other ways.
"""

def contact_form(request):

    serializer = ContactMessageSerializer()
    context = {
        'serializer': serializer
    }

    return render(request, 'meant/contact.html', context)

class MessageViewSet(viewsets.ModelViewSet):
    permission_classes = (AdminOnly,)
    queryset = Contact.objects.all()
    pagination_class = ContactMessagePaginator
    serializer_class = ContactMessageSerializer
    filterset_class  = ContactMessageFilter

    def create(self, request):
        serializer = ContactMessageSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

serializers.py

TEMPLATE_PATH = os.path.join(
                settings.TEMPLATES[0]["DIRS"][0], "base", "custom_input_forms.html"
            )
CHOICE_CHECK = set(map(operator.itemgetter(0), Contact.SUBJECT))

class ContactMessageSerializer(serializers.ModelSerializer):

    status = serializers.SerializerMethodField()
    subject = serializers.ChoiceField(choices=Contact.SUBJECT)

    name = serializers.CharField(
        style={
            "template": TEMPLATE_PATH,
            "class": "custom_form_control",
            "input_type": "input"
        }
    )

    email = serializers.CharField(
        style={
            "template": TEMPLATE_PATH,
            "class": "custom_form_control",
            "input_type": "input"
        }
    )

    subject = serializers.CharField(
        style={
            "template": TEMPLATE_PATH,
            "class": "custom_form_control",
            "input_type": "select",
            "choice": Contact.SUBJECT
        }
    )

    message = serializers.CharField(
        style={
            "template": TEMPLATE_PATH,
            "input_type": "textarea",
        }
    )

    def get_status(self, obj):
        return obj.status

    def validate(self, data):
        name = data["name"]
        email = data["email"]
        choice = data["subject"]

        if (5 > len(name)):
            raise serializers.ValidationError({"Invalid name": "Name is to short."})
        try:
            validate_email(email)
        except:
            raise serializers.ValidationError({"Invalid Email": "Email is invalid, please recheck."})

        if not (choice in CHOICE_CHECK):
            choices_printable = ', '.join(CHOICE_CHECK)
            raise serializers.ValidationError({"Invalid Choice": f"Choice field is not valid. Valid choices are: {choices_printable}"})

        return data

    class Meta:
        model = Contact
        fields = "__all__"

tests.py

class MessageViewSetTest(APITestCase):
    def setUp(self) -> None:

        self.admin_user = Client()
        self.base_user = Client()

        self.user = User.objects.create_superuser(
            username='foobar',
            email='[email protected]',
            password='hatefoobarpeople')

        self.admin_user.force_login(user=self.user)

        self.message_dummy = {
            'name': 'Test User',
            'email': '[email protected]',
            'subject': 'app',
            'message': 'one test please'
        }


    def test_get_messages_admin(self):

        response = self.admin_user.get(reverse('message-list'))
        self.assertEqual(response.status_code, 200)
    
    def test_get_messages_all(self):

        response = self.base_user.get(reverse('message-list'))
        self.assertEqual(response.status_code, 403)

    def test_post_correct_payload(self):

        response_base = self.base_user.post(reverse('message-list'), self.message_dummy)
        response_admin = self.admin_user.post(reverse('message-list'), self.message_dummy)
  
        self.assertEqual(response_base.status_code, 201)
        self.assertEqual(response_admin.status_code, 201)
        self.assertEqual(response_admin.data['name'], self.message_dummy['name'])

    def test_post_incorrect_payload(self):

        self.message_dummy['subject'] = "this cant work"

        response_base = self.base_user.post(reverse('message-list'), self.message_dummy)
        response_admin = self.admin_user.post(reverse('message-list'), self.message_dummy)

        self.assertEqual(response_base.status_code, 400)
        self.assertEqual(response_admin.status_code, 400)
        

    """
        We can test serializers to but in this case I don't think thats nessesary 
        becouse we are doing full request-response cycle
        Regardles of that here is one for fun
    """

    def test_email_valid(self):
        
        self.message_dummy['email'] = 'emaildomain.com'
        serializer = ContactMessageSerializer(data=self.message_dummy)

        self.assertFalse(serializer.is_valid())
        self.assertEqual(set(serializer.errors.keys()), set(['Invalid Email']))

permissions

class AdminOnly(BasePermission):

    def has_permission(self, request, view):
        if view.action == 'create':
            return True
        return request.user.is_superuser

    def has_object_permission(self, request, view, obj):
        return view.action in ['list', 'retrieve', 'update', 'partial_update', ] and request.user.is_superuser
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Welcome to Code Review! I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Feel free to edit and give it a different title if there is something more appropriate. \$\endgroup\$ Apr 24 at 23:45

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.