ModelMultipleChoiceField returns queryset instead of object instance - python

Hello I have this problem with multiple checkbox select
I have this form
class AnswerForm(forms.ModelForm):
class Meta:
model = Response
fields = ('answer', )
def __init__(self, *args, **kwargs):
question = kwargs.pop('question')
super().__init__(*args, **kwargs)
self.fields['answer'].queryset = question.answers.order_by('text')
if question.question_field_type == 'multiple':
self.fields['answer'] = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple(
attrs={'autocomplete': 'off'}),
queryset=question.answers.order_by('text'),
)
My problem is that on submission it raises this error
"<QuerySet [<Answer: A. High School Diploma>, <Answer: B. Associate's Degree>]>": "Response.answer" must be a "Answer" instance.
how do make these to return answer instance instead of a queryset.
model
class Question(models.Model):
RADIO = 'radio'
SELECT = 'select'
TEXT = 'text'
MULTI = 'multiple'
QUESTION_TYPES = (
(RADIO, 'radio'),
(SELECT, 'select'),
(TEXT, 'text'),
(MULTI, 'multiple'),
)
text = models.CharField(
max_length=512,
help_text="this text will be displayed to the user taking the survey"
)
question_number = models.PositiveIntegerField(
default=0,
help_text='Use for question ordering, by default questions are ordered by text'
)
partner_related_question = models.BooleanField(
default=False,
help_text='this will be used to match ideal partner against user choices'
)
question_field_type = models.CharField(
choices=QUESTION_TYPES, max_length=15)
created_date = models.DateField(auto_now_add=True)
objects = QuestionManager()
class Meta:
ordering = ['question_number']
def __str__(self):
return self.text
class Answer(models.Model):
question = models.ForeignKey(
Question, on_delete=models.CASCADE, related_name='answers')
text = models.CharField('Answer', max_length=512)
def __str__(self):
return self.text
class Response(models.Model):
person = models.ForeignKey(
Person, on_delete=models.CASCADE, related_name='person_answers')
answer = models.ForeignKey(
Answer, on_delete=models.CASCADE, related_name='+')
created = models.DateTimeField(default=now, editable=False)

I think you should use ManyToManyField in Response class instead of ForeignKey:
class Response(models.Model):
...
answers = models.ManyToManyField(Answer)
...
# forms.py
class AnswerForm(forms.ModelForm):
class Meta:
model = Response
fields = ('answers', )
then in the case you are using CBV CreateView, override form_valid as this:
def form_valid(self, form):
self.object = form.save()
for answer in form.cleaned_data['answers']:
answer.save()
self.object.answers.add(answer)
return HttpResponseRedirect(self.get_success_url())
remember to add the get_success_url() method to Response model

Related

How to return correct model fields in serializer?

I am implementing a search bar that returns the users and the posts.
I am able to return the data but when I clear the search bar I get the error returned:
AttributeError: Got AttributeError when attempting to get a value for field username on serializer SearchUserSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Post instance.
The original exception text was: 'Post' object has no attribute 'username'.
My Models:
class User(AbstractUser):
avi_pic = models.ImageField(
max_length=400, null=True, blank=True, upload_to='images')
name = models.CharField(max_length=50, blank=True, null=True)
username = models.CharField(max_length=30, unique=True)
class Post(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE, default=None
)
cover = models.CharField(max_length=300, default='', blank=True)
title = models.CharField(max_length=300, default='', blank=True)
date = models.DateTimeField(editable=False, auto_now_add=True)
My Serializers:
class SearchPostSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('id', 'title', 'user', 'username', 'cover')
def get_username(self, post):
return post.user.username
class SearchUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'name', 'username', 'avi_pic')
and my Views.py:
class SearchView(generics.ListAPIView):
def get_serializer_class(self):
queryset = self.get_queryset()
if len(queryset) == 0:
return None
if isinstance(queryset[0], User):
return SearchUserSerializer
elif isinstance(queryset[0], Post):
return SearchPostSerializer
else:
return None
def get_queryset(self):
query = self.request.query_params.get('q', None)
if query is not None:
queryset_users = User.objects.filter(
Q(name__icontains=query) | Q(username__icontains=query))
queryset_posts = Post.objects.filter(
Q(playlist_title__icontains=query))
results = list(queryset_users) + list(queryset_posts)
return results
else:
return User.objects.none()
I've tried many things with zero luck.
Try this:
class SearchPostSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
class Meta:
model = Post
fields = ('id', 'title', 'user', 'username', 'cover')
This should do the trick .
You can remove the get_username function
You can take a look here for the explanation:
https://www.django-rest-framework.org/api-guide/fields/#source

How to access atribute of instance I want to validate from inside serializer?

In my view I do following:
class ReviewViewSet(viewsets.ModelViewSet):
#queryset, serializer_class and permission_classes defined here
def perform_create(self, serializer):
title_id = self.kwargs.get('title_id')
title = get_object_or_404(Title, pk=title_id)
serializer.save(author=self.request.user, title=title)
I want to validate that review doesn't exist yet. I'm trying to do this in serializer's validate():
class ReviewSerializer(serializers.ModelSerializer):
title = serializers.SlugRelatedField(slug_field='pk', read_only='True')
author = serializers.SlugRelatedField(slug_field='username', read_only='True')
def validate(self, data):
title = # <-- How to get title here?
author = self.context['request'].user
queryset = Review.objects.all().filter(title=title, author=author)
if queryset.exists():
raise serializers.ValidationError('Review alredy exists')
return(data)
class Meta:
fields = '__all__'
model = Review
Attempt to do title = self.title raises AttributeError: 'ReviewSerializer' object has no attribute 'title'
How to access title from inside validate() in my case?
Here's my Review model:
class Review(models.Model):
class Meta:
ordering = ['-pub_date']
unique_together = ['title', 'author']
title = models.ForeignKey(
Title,
on_delete=models.CASCADE,
related_name='reviews',
)
author = models.ForeignKey(
CustomUser,
on_delete=models.CASCADE,
related_name='reviews',
)
text = models.TextField('Review text')
score = models.PositiveSmallIntegerField(
'Review score',
validators=[
MinValueValidator(1),
MaxValueValidator(10)
]
)
pub_date = models.DateTimeField(
'Date and time of review',
auto_now_add=True,
db_index=True
)
The response to your question lies in the documentation : https://www.django-rest-framework.org/api-guide/serializers/#object-level-validation
About the validate method:
This method takes a single argument, which is a dictionary of field values
If you look at the code sample in the doc, you'll see that all data that you might need to validate are in the data argument of the validate method.
Hence, the title is in data['title'].

Django updateView saving another instance instead of updating

im trying to update my model but it just creates another instance and i cant figure out why. i was under the impression that all i needed was:
class QuestionUpdate(generic.UpdateView):
model = models.Question
form_class = QuestionForm
and it would take care of it for me but that doesnt seem to be the case.
im in django 1.11 and running python 3.6. Any and all help is appreciated.
models.py
class Question(models.Model):
class Meta:
ordering = ['-date_updated']
# user = models.ForeignKey(User, related_name="question", default='')
question = models.TextField(unique=False, blank=False, null=False)
question_html = models.TextField(blank=False, null=False)
answer = models.TextField(blank=False, null=False)
answer_html = models.TextField(blank=False,null=False)
date_created = models.DateTimeField(auto_now=True, null=True)
date_updated = models.DateTimeField(auto_now=True, null=True)
def __str__(self):
return self.question
# ^ to display an object in the Django admin site and
# as the value inserted into a template when it displays an object.
def save(self, *args, **kwargs):
self.question_html = misaka.html(self.question)
self.answer_html = misaka.html(self.answer)
super().save(*args, **kwargs)
views.py
class QuestionUpdate(generic.UpdateView):
model = models.Question
form_class = QuestionForm
# fields = ('question', 'answer')
def edit_question(self, request, id):
question = get_object_or_404(Question, id=id)
form = QuestionForm(request.POST, instance=question)
if form.is_valid():
form.save()
forms.py
class QuestionForm(forms.ModelForm):
# your_name = forms.CharField(label='Your name', max_length=100)
class Meta:
fields = ("question", 'answer')
model = models.Question
urls.py
url(r'questionupdate/(?P<pk>\d+)/$', views.QuestionUpdate.as_view(), name='update'),

Django Rest Framework writable nested field using create()

I'm trying to write a create method that will write my nested fields but am finding that the nested object isn't written.
This is the sample I was using:
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ('username', 'email', 'profile')
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
But I'm failing to understand what the user=user refers to.
Here is my code:
class MessagesSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(source='pk', read_only=True)
suggested_songs = SongSerializer()
class Meta:
model = Messages
fields = ('id','owner','url','suggested_songs',)
#fields = ('id','url','suggested_songs',)
def create(self, validated_data):
song_data = validated_data.pop('suggested_songs')
message = Messages.objects.create(**validated_data)
Song.objects.create(**song_data)
return message
class SongSerializer(serializers.HyperlinkedModelSerializer):
#id = serializers.IntegerField(source='pk', read_only=True)
class Meta:
model = Song
fields = ('id','title','artist','album','albumId','num_votes','cleared')
read_only_fields = ('song_id')
class Messages(models.Model):
owner = models.OneToOneField(User, primary_key=True, related_name='user_messages', editable=False) #TODO, change owner to 'To'
#suggested_songs = models.ManyToManyField(Song, related_name='suggested_songs')
suggested_songs = models.ForeignKey(Song, null=True, blank=True)
# If a user is added, this runs.
#receiver(post_save, sender=User)
def create_friend_for_user(sender, instance=None, created=False, **kwargs):
if created:
Messages.objects.get_or_create(owner=instance)
# Same as above, but for deletion
#receiver(pre_delete, sender=User)
def delete_friend_for_user(sender, instance=None, **kwargs):
if instance:
Messages.objects.get(owner=instance).delete()
class Song(models.Model):
"""
A model which holds information about the songs.
"""
#song_id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=150, blank=True, default='')
artist = models.CharField(max_length=150, blank=True, default='')
album = models.CharField(max_length=150, blank=True, default='')
albumId = models.CharField(max_length=150, blank=True, default='')
num_votes = models.IntegerField(default=0, blank=True)
cleared = models.BooleanField(default=False, blank=True)
class Meta:
ordering = ('title',)
#managed=True
I think that the issue might be in the MessageSerializer.create method:
def create(self, validated_data):
# here you are popping the suggested songs
song_data = validated_data.pop('suggested_songs')
# so when you create the message here the foreign key is set to NULL
message = Messages.objects.create(**validated_data)
# and here you create the Song instance correctly but it is not
# associated with the message
Song.objects.create(**song_data)
return message
You need to pass the foreign key to the Messages.create method like in the example you have.
def create(self, validated_data):
song_data = validated_data.pop('suggested_songs')
song = Song.objects.create(**song_data)
# song need to be created first because the foreign key is in
# the Messages model
message = Messages.objects.create(suggested_songs=song, **validated_data)
return message
I hope this helps!

Adding 2 models inside a Admin Manager

I have 2 models, Question and Image. I want to create a manager in django admin, to include the fields of Image inside the Question admin panel.
These are the models:
class Question(models.Model):
quiz = models.ManyToManyField(Quiz, blank=True, )
category = models.ForeignKey(Category, blank=True, null=True, )
content = models.CharField(max_length=1000,
blank=False,
help_text="Enter the question text that you want displayed",
verbose_name='Question',
)
explanation = models.TextField(max_length=2000,
blank=True,
help_text="Explanation to be shown after the question has been answered.",
verbose_name='Explanation',
)
class Meta:
verbose_name = "Question"
verbose_name_plural = "Questions"
ordering = ['category']
def __unicode__(self):
return self.content
class Image(models.Model):
TYPE_CHOICES = (
('A','Answer'),
('Q','Question'),
)
image = models.ImageField(upload_to='static/img')
type = models.CharField(max_length=1, choices=TYPE_CHOICES)
question = models.ForeignKey(Question, blank=True, null=True)
answer = models.ForeignKey(Answer, blank=True, null=True)
def __unicode__(self):
return self.type
This is the Question Manager in Django Admin:
class QuizAdminForm(forms.ModelForm):
class Meta:
model = Quiz
questions = forms.ModelMultipleChoiceField(
queryset=Question.objects.all(),
required=False,
widget=FilteredSelectMultiple(verbose_name=('Questions'),
is_stacked=False )
)
def __init__(self, *args, **kwargs):
super(QuizAdminForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['questions'].initial = self.instance.question_set.all()
def save(self, commit=True):
quiz = super(QuizAdminForm, self).save(commit=False)
if commit:
quiz.save()
if quiz.pk:
quiz.question_set = self.cleaned_data['questions']
self.save_m2m()
return quiz
You are looking InlineModelAdmin models.
class ImageInline(admin.TabularInline):
model = Image
...
class QuestionAdmin(admin.ModelAdmin):
list_display = ('content', 'category', )
list_filter = ('category',)
fields = ('content', 'category', 'quiz', 'explanation')
search_fields = ('content', 'explanation')
filter_horizontal = ('quiz',)
inlines = [AnswerInline, ImageInline]
https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#inlinemodeladmin-objects
Good to see you are using Django Quiz app. I have recently added a lot of changes to it and it would be good if you could contribute anything to the repo:
https://github.com/tomwalker/django_quiz

Categories

Resources