Serrar o questionário da Web Meduza: um guia passo a passo para iniciantes

Meu nome é Egor, sou desenvolvedor full-stack no Leader-ID. Neste artigo, quero compartilhar uma receita simples para criar um questionário baseado na Web bonito e conveniente, semelhante aos criados pela Meduza. Ele sabe como mostrar estatísticas depois de responder a perguntas individuais, calcular a pontuação total, emitir comentários, fazer upload de dados para análise e atrapalhar os resultados nas redes sociais. Para fazer isso, escolhi o Django, DRF, Python e o banco de dados PostgreSQL.



Todos os detalhes estão sob o corte.

Depois de uma hora olhando para a alvenaria (lição complicada, no entanto) , o primeiro resultado apareceu na forma de modelos prontos, que foram descritos em Dzhang após dez minutos.

Se você é iniciante, aconselho a seguir o tutorial do Djnago , que descreve como criar uma pesquisa passo a passo. E após o tutorial DRF , para finalmente mergulhar no tópico.

Então, no projeto, eu usei:

  • Django 3.0.3. Para o back-end;
  • django-rest-framework. Para criar rest-api;
  • Pitão
  • PostgreSQL como banco de dados;
  • Front-end - Nuxt.js, Axios, UI do elemento.

Agora os passos


pip install Django - instale a biblioteca.

django-admin startproject core - crie um projeto djang.

cd core - vá para o diretório com o projeto.

pools startapp python manage.py - adicione um aplicativo de pesquisa.

Em seguida, descrevemos os modelos em models.py nas pesquisas e criamos um serializador para 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


Codifique o texto aqui
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()


Em seguida, escrevemos duas visualizações DRF em views.py, que fornecem todas as perguntas com opções e aceitam todas as respostas do usuário.


Codifique o texto aqui
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'})


Agora descrevemos os links em urls.py:

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

Adicione modelos ao admin.py:


Codifique o texto aqui
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)


O próximo passo é adicionar nosso aplicativo de enquetes a settings.py (no diretório principal) em INSTALLED_APPS. E execute o comando de inicialização:

  • python manage.py makemigrations - crie uma migração para os modelos criados
  • python manage.py migrate - executa uma migração para o banco de dados
  • python manage.py createuperuser - cria um superusuário (admin)
  • python manage.py runserver - inicia o servidor

Para calcular a pontuação total das perguntas, adicionamos ao método uma função de cálculo simples, de acordo com a qual os pontos são calculados. Não publicarei o código de função, pois ele pode ser usado para decifrar nosso questionário. Agora cada resposta tem seu próprio "peso".

Vamos ao painel de administração através do navegador usando o link fornecido no console ( http://127.0.0.1:8000 / admin por padrão) e criamos perguntas e respostas para eles, colocando pontos.



Era importante para mim fornecer aos nossos parceiros listas de pessoas que responderam à pesquisa e suas respostas. Mas, para isso, não basta vincular as respostas às perguntas. Portanto, adicionei outra tabela - "Opções". Portanto, foi formada uma conexão entre as respostas do usuário e as perguntas com várias opções de respostas. Isso nos permite fazer o upload de dados no formato em que os parceiros podem interpretá-los facilmente.

Como resultado, a estrutura do banco de dados ficou assim:


Agora conecte a frente.

Nele retiramos a lista de perguntas e respostas, passamos por cada elemento até o último. Dependendo do tipo de pergunta, alteramos o componente com sua própria lógica e estilo. Portanto, quando não há mais perguntas na lista, enviamos o resultado para trás e obtemos uma resposta com o número de pontos. Depois de receber o número de pontos, abrimos a página de resultados; se houver pontos, não mostramos mais perguntas.

Nesta fase, já temos um questionário pronto que pode fazer tudo o que é necessário: fazer perguntas, receber e coletar respostas, fornecer resultados na forma de pontos e comentários.

Adicionar pãezinhos


Primeiro, eu precisava fazer upload dos dados periodicamente. Para isso, acabei de adicionar o comando de gerenciamento.

Em segundo lugar, seria bom implementar o compartilhamento dos resultados da pesquisa nas redes sociais. ESTÁ BEM. Funcionalidade de serrar que permite compartilhar uma imagem com os pontos VKontakte e Facebook.

Geramos cem variantes de imagens refletindo pontos para o VK e o Facebook separadamente (diferentes resoluções). Agora, conectamos a transferência do link à imagem no componente social da parte do front-end. Com o VKontakte, tudo acabou sendo simples: passamos o parâmetro de imagem com o URL direto para o desejado. Mas eu tive que mexer no Facebook. Acontece que eles não aceitam mídia pela API e, se eu transferi uma imagem ou imagem do URL da imagem, um grande campo vazio é mostrado na postagem. Como se viu depois, ele tira uma foto do metainf (og: image) do próprio site, que ele compartilhou (passamos o parâmetro u no link). E ela, entre outras coisas, teve que ser mudada dinamicamente. Eu não queria fazer redirecionamentos desnecessários e um mecânico na parte de trás, e decidi converter o SPA (aplicativo de página única) em SSR (renderização no servidor) na frente,de modo que, dependendo da solicitação, o URL da imagem com uma pontuação em meta-meta muda antes que o JavaScript seja lançado no navegador. Felizmente, a estrutura do Nuxt.js, tomada como base, permite que isso seja feito simplesmente alternando os modos. Agora resta esboçar tags apenas do cliente e adicionar a lógica para alterar o cabeçalho da presença de uma pontuação de consulta.



Além disso, no servidor, era necessário iniciar o serviço daemon para fornecer as páginas geradas e deixar a estatística para o nginx também. Todo o lucro!



Revivemos o questionário


Para manter o nível de interesse dos participantes no processo de preenchimento da pesquisa, adicionei uma exibição dinâmica de estatísticas a cada pergunta individual. Depois de responder à pergunta, o usuário vê como os outros responderam. Às vezes, não está claro para uma pessoa por que essas perguntas são feitas. Portanto, suplementei cada pergunta com explicações engraçadas. Bem, o truque mais importante para revitalizar o meu questionário foi realizado pelos designers da nossa empresa.



Sumário


Essas pesquisas de mídia são bastante simples de implementar e, mais importante, os usuários realmente gostam delas. Eles podem ser usados ​​tanto na cauda quanto na crina: para pesquisa sociológica, informando / testando conhecimentos ou criando elementos interativos em sites e serviços. Tentei descrever em detalhes o processo de sua criação, mas se você tiver alguma dúvida, entre em contato. Exemplos de pesquisas nesse mecanismo podem ser vistos nesses dois links: healthcare.leader-id.ru e covid.leader-id.ru .

All Articles