Scier le questionnaire Web Meduza: un guide pas à pas pour les débutants

Je m'appelle Egor, je suis développeur Full-stack en Leader-ID. Dans cet article, je veux partager une recette simple pour créer un questionnaire Web beau et pratique comme ceux créés par Meduza. Il sait comment afficher des statistiques aprÚs avoir répondu à des questions individuelles, calculer le score total, émettre des commentaires, télécharger des données pour analyse et échapper des résultats sur les réseaux sociaux. Pour ce faire, j'ai choisi Django, DRF, Python et la base de données PostgreSQL.



Tous les détails sont sous la coupe.

AprĂšs une heure Ă  regarder la maçonnerie (leçon collante, cependant) , le premier rĂ©sultat est apparu sous la forme de modĂšles prĂȘts Ă  l'emploi, qui ont Ă©tĂ© dĂ©crits Ă  Dzhang aprĂšs dix minutes.

Si vous ĂȘtes dĂ©butant, je vous conseille de passer par le tutoriel Djnago , il dĂ©crit comment crĂ©er une enquĂȘte Ă©tape par Ă©tape. Et aprĂšs le tutoriel DRF , pour enfin plonger dans le sujet.

Donc, dans le projet, j'ai utilisé:

  • Django 3.0.3. Pour le backend;
  • django-rest-framework. Pour crĂ©er rest-api;
  • Python
  • PostgreSQL en tant que base de donnĂ©es;
  • Front-end - Nuxt.js, Axios, Element-UI.

Maintenant les Ă©tapes


pip install Django - installe la bibliothĂšque.

django-admin startproject core - crée un projet djang.

cd core - allez dans le répertoire avec le projet.

pools de startapp python manage.py - ajoutez une application d'interrogation.

Ensuite, nous décrivons les modÚles dans models.py dans les sondages et créons un sérialiseur pour DRF.

class Question(models.Model):
    title = models.CharField(max_length=4096)
    visible = models.BooleanField(default=False)
    max_points = models.FloatField()

    def __str__(self):
           return self.title

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.DO_NOTHING)
    title = models.CharField(max_length=4096)
    points = models.FloatField()
    lock_other = models.BooleanField(default=False)

    def __str__(self):
        return self.title

class Answer(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING)
    question = models.ForeignKey(Question, on_delete=models.DO_NOTHING)
    choice = models.ForeignKey(Choice, on_delete=models.DO_NOTHING)
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.choice.title


Code texte ici
from rest_framework import serializers
from .models import Answer, Question, Choice


class ChoiceSerializer(serializers.ModelSerializer):
    percent = serializers.SerializerMethodField()

    class Meta:
        model = Choice
        fields = ['pk', 'title', 'points', 'percent', 'lock_other', ]

    def get_percent(self, obj):
        total = Answer.objects.filter(question=obj.question).count()
        current = Answer.objects.filter(question=obj.question, choice=obj).count()
        if total != 0:
            return float(current * 100 / total)
        else:
            return float(0)


class QuestionSerializer(serializers.ModelSerializer):
    choices = ChoiceSerializer(many=True, source='choice_set', )

    class Meta:
        model = Question
        fields = ['pk', 'title', 'choices', 'max_points', ]


class AnswerSerializer(serializers.Serializer):
    answers = serializers.JSONField()

    def validate_answers(self, answers):
        if not answers:
            raise serializers.Validationerror("Answers must be not null.")
        return answers

    def save(self):
        answers = self.data['answers']
        user = self.context.user
        for question_id, in answers:  #     ,    
            question = Question.objects.get(pk=question_id)
            choices = answers[question_id]
            for choice_id in choices:
                choice = Choice.objects.get(pk=choice_id)
                Answer(user=user, question=question, choice=choice).save()
                user.is_answer = True
                user.save()


Ensuite, nous écrivons deux vues DRF dans views.py, qui donnent toutes les questions avec des options et acceptent toutes les réponses de l'utilisateur.


Code texte ici
from .serializers import QuestionSerializer, AnswerSerializer
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from .models import Question


class GetQuestion(GenericAPIView):
    permission_classes = (IsAuthenticated,)
    serializer_class = QuestionSerializer

    def get(self, request, format=None):
        questions = Question.objects.filter(visible=True, )
        last_point = QuestionSerializer(questions, many=True)
        return Response(last_point.data)


class QuestionAnswer(GenericAPIView):
    permission_classes = (IsAuthenticated,)
    serializer_class = AnswerSerializer

    def post(self, request, format=None):
        answer = AnswerSerializer(data=request.data, context=request)
        if answer.is_valid(raise_exception=True):
            answer.save()
            return Response({'result': 'OK'})


Nous décrivons maintenant les liens dans urls.py:

urlpatterns = [
    path('', GetQuestion.as_view()),
    path('answer/', QuestionAnswer.as_view()),
]

Ajoutez des modĂšles Ă  admin.py:


Code texte ici
from django.contrib import admin
from .models import Question, Answer, Choice


class QuestionAdmin(admin.ModelAdmin):
    list_display = (
        'title',
        'visible',
        'max_points',
    )


class ChoiceAdmin(admin.ModelAdmin):
    list_display = (
        'title',
        'question',
        'points',
        'lock_other',
    )
    list_filter = ('question',)


class AnswerAdmin(admin.ModelAdmin):
    list_display = (
        'user',
        'question',
        'choice',
    )
    list_filter = ('user',)


admin.site.register(Question, QuestionAdmin)
admin.site.register(Choice, ChoiceAdmin)
admin.site.register(Answer, AnswerAdmin)


L'étape suivante consiste à ajouter notre application de sondages à settings.py (dans le répertoire principal) dans INSTALLED_APPS. Et exécutez la commande de lancement:

  • python manage.py makemigrations - crĂ©e une migration pour les modĂšles crĂ©Ă©s
  • python manage.py migrate - effectuer une migration vers la base de donnĂ©es
  • python manage.py createuperuser - crĂ©e un superutilisateur (admin)
  • python manage.py runserver - dĂ©marre le serveur

Pour calculer le score total des questions, nous ajoutons Ă  la mĂ©thode une fonction de calcul simple, selon laquelle les points sont calculĂ©s. Je ne publierai pas le code de fonction, car il peut ĂȘtre utilisĂ© pour dĂ©chiffrer notre questionnaire. Maintenant, chaque rĂ©ponse a son propre «poids».

Nous accédons au panneau d'administration via le navigateur en utilisant le lien fourni dans la console ( http://127.0.0.1:8000 / admin par défaut), et créons des questions et des réponses pour eux, notez les points.



Il Ă©tait important pour moi de donner Ă  nos partenaires des listes de personnes ayant rĂ©pondu Ă  l'enquĂȘte et leurs rĂ©ponses. Mais pour cela, il ne suffit pas de lier simplement les rĂ©ponses aux questions. Par consĂ©quent, j'ai ajoutĂ© un autre tableau - «Options». Ainsi, une connexion a Ă©tĂ© Ă©tablie entre les rĂ©ponses de l'utilisateur aux questions avec plusieurs options de rĂ©ponse. Cela nous permet de tĂ©lĂ©charger des donnĂ©es sous la forme dans laquelle les partenaires peuvent facilement les interprĂ©ter.

En conséquence, la structure de la base de données s'est révélée comme suit:


Connectez maintenant le devant.

On y retire la liste des questions et réponses, on passe en revue chaque élément jusqu'au dernier. Selon le type de question, nous modifions le composant avec sa propre logique et son propre style. En conséquence, quand il n'y a plus de questions dans la liste, nous envoyons le résultat à l'arriÚre et obtenons une réponse avec le nombre de points. AprÚs avoir reçu le nombre de points, nous ouvrons la page de résultats, s'il y a des points, nous ne montrons plus de questions.

À ce stade, nous avons dĂ©jĂ  prĂ©parĂ© un questionnaire qui peut faire tout ce dont il a besoin: poser des questions, recevoir et recueillir des rĂ©ponses, donner le rĂ©sultat sous forme de points et de commentaires.

Ajouter des petits pains


Tout d'abord, je devais télécharger réguliÚrement les données. Pour cela, je viens d'ajouter la commande de gestion.

DeuxiĂšmement, il serait intĂ©ressant de mettre en Ɠuvre le partage des rĂ©sultats de l'enquĂȘte sur les rĂ©seaux sociaux. D'ACCORD. FonctionnalitĂ© de sciage qui vous permettra de partager une photo avec des points VKontakte et Facebook.

Nous gĂ©nĂ©rons une centaine de variantes d'images reflĂ©tant les points pour VK et Facebook sĂ©parĂ©ment (diffĂ©rentes rĂ©solutions). Maintenant, nous connectons le transfert du lien Ă  l'image dans la composante sociale de la partie front-end. Avec VKontakte, tout s'est avĂ©rĂ© simple: nous passons le paramĂštre image avec l'URL directe Ă  celui souhaitĂ©. Mais j'ai dĂ» bricoler avec Facebook. Il s'est avĂ©rĂ© qu'ils n'acceptent pas les mĂ©dias par API, et si j'ai transfĂ©rĂ© une image ou une image Ă  partir de l'URL de l'image, un grand champ vide a Ă©tĂ© affichĂ© dans le message. Comme il s'est avĂ©rĂ© plus tard, il prend une photo de la mĂ©tainf (og: image) du site lui-mĂȘme, qu'il a partagĂ© (nous passons le paramĂštre u dans le lien). Et elle, entre autres, a dĂ» ĂȘtre changĂ©e dynamiquement. Je ne voulais pas faire de redirections inutiles et un mĂ©canicien Ă  l'arriĂšre, et j'ai dĂ©cidĂ© de convertir SPA (application d'une seule page) en SSR (rendu cĂŽtĂ© serveur) Ă  l'avant,de sorte qu'en fonction de la demande, l'url de l'image avec un score en tĂȘte-mĂ©ta change avant que JavaScript ne soit lancĂ© dans le navigateur. Heureusement, le framework Nuxt.js pris comme base permet de le faire simplement en changeant de mode. Il ne reste plus qu'Ă  esquisser les balises client uniquement et ajouter la logique pour changer la tĂȘte de la prĂ©sence d'un score de requĂȘte.



De plus, sur le serveur, il était nécessaire de démarrer le service démon pour donner les pages générées, et de laisser la statique à nginx également. Tout profit!



Nous relançons le questionnaire


Afin de maintenir le niveau d'intĂ©rĂȘt des participants dans le processus de remplissage de l'enquĂȘte, j'ai ajoutĂ© un affichage dynamique des statistiques Ă  chaque question individuelle. AprĂšs avoir rĂ©pondu Ă  la question, l'utilisateur voit comment les autres ont rĂ©pondu. Parfois, il n'est pas clair pour une personne pourquoi on lui pose ces questions. Par consĂ©quent, j'ai complĂ©tĂ© chaque question avec des explications amusantes. Eh bien, l'astuce la plus importante pour revitaliser mon questionnaire a Ă©tĂ© rĂ©alisĂ©e par les designers de notre entreprise.



Sommaire


De telles enquĂȘtes mĂ©diatiques sont assez simples Ă  mettre en Ɠuvre et, surtout, les utilisateurs les aiment vraiment. Ils peuvent ĂȘtre utilisĂ©s Ă  la fois dans la queue et dans la criniĂšre: pour la recherche sociologique, informer / tester les connaissances ou crĂ©er des Ă©lĂ©ments interactifs sur les sites et services. J'ai essayĂ© de dĂ©crire en dĂ©tail le processus de leur crĂ©ation, mais si vous avez des questions, n'hĂ©sitez pas Ă  commenter. Des exemples d'enquĂȘtes sur ce moteur peuvent ĂȘtre consultĂ©s sur ces deux liens: Healthcare.leader-id.ru et covid.leader-id.ru .

All Articles