I have a slug field for a model that I would like returned in the object representation but NOT as part of the form input in the browsable API. It is generated by a slugify method on the model.
When I mark it as read only in it's ModelSerializer by adding it to Meta using read_only_fields=('slug',) trying to add new fields in the browseable api form yields "This field is required."
The serializer for reference is below:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
slug = serializers.SlugField(read_only=True, required=False)
def to_representation(self, obj):
self.fields['children'] = CategorySerializer(obj, many=True, read_only=True)
return super(CategorySerializer, self).to_representation(obj)
class Meta:
model = Category
fields = ('pk', 'url', 'title', 'slug', 'parent', 'children', 'active', 'icon')
read_only_fields = ('children','slug',)
What is a simple solution to show the field in the representation and not the browseable api form given the above?
For reference, here is my model:
#python_2_unicode_compatible
class CategoryBase(mptt_models.MPTTModel):
parent = mptt_fields.TreeForeignKey( 'self', blank=True, null=True, related_name='children', verbose_name=_('parent'))
title = models.CharField(max_length=100, verbose_name=_('name'))
slug = models.SlugField(verbose_name=_('slug'), null=True)
active = models.BooleanField(default=True, verbose_name=_('active'))
objects = CategoryManager()
tree = TreeManager()
def save(self, *args, **kwargs):
"""
While you can activate an item without activating its descendants,
It doesn't make sense that you can deactivate an item and have its
decendants remain active.
"""
if not self.slug:
self.slug = slugify(self.title)
super(CategoryBase, self).save(*args, **kwargs)
if not self.active:
for item in self.get_descendants():
if item.active != self.active:
item.active = self.active
item.save()
def __str__(self):
ancestors = self.get_ancestors()
return ' > '.join([force_text(i.title) for i in ancestors] + [self.title, ])
class Meta:
abstract = True
unique_together = ('parent', 'slug')
ordering = ('tree_id', 'lft')
class MPTTMeta:
order_insertion_by = 'title'
class Category(CategoryBase):
icon = IconField(null=True, blank=True)
order = models.IntegerField(default=0)
#property
def short_title(self):
return self.title
def get_absolute_url(self):
"""Return a path"""
from django.core.urlresolvers import NoReverseMatch
try:
prefix = reverse('categories_tree_list')
except NoReverseMatch:
prefix = '/'
ancestors = list(self.get_ancestors()) + [self, ]
return prefix + '/'.join([force_text(i.slug) for i in ancestors]) + '/'
def save(self, *args, **kwargs):
super(Category, self).save(*args, **kwargs)
class Meta(CategoryBase.Meta):
verbose_name = _('category')
verbose_name_plural = _('categories')
Related
views.py
class ReviewList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Review.objects.all()
serializer_class = ReviewSerializer
def get(self,request,*args,**kwargs):
return self.list(request, *args, **kwargs)
def post(self,request,*args,**kwargs):
return self.create(request, *args, **kwargs)
models.py
class Review(models.Model):
rating = models.PositiveIntegerField(validators=[MinValueValidator(1),MaxValueValidator(5)])
description = models.CharField(max_length=200, null=True)
created = models.DateTimeField(auto_now_add=True)
update = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)
watchlist = models.ForeignKey(WatchList, on_delete=models.CASCADE, related_name='reviews')
def __str__(self) -> str:
return str(self.rating) + ' - ' + self.watchlist.title
urls.py
urlpatterns = [
path('list/', WatchListAV.as_view(), name='movie-list'),
path('<int:pk>', MovieDetailsAV.as_view(),name='movie-details'),
path('stream/',StreamPlatformAV.as_view(),name='stream-list'),
path('stream/<int:pk>', StreamDetailAV.as_view(), name="stream-detail"),
path('review/', ReviewList.as_view(),name='review-list'),
]
serializers.py
class ReviewSerializer(serializers.Serializer):
class Meta:
model = Review
fields = '__all__'
the list of reviews return empty
as attached in photo the list of reviews is empty, im new to django cant figure it out
When creating serializers for a model you should subclass ModelSerializer
class ReviewSerializer(serializers.ModelSerializer):
class Meta:
model = Review
fields = '__all__'
I have a view inside posts app where I try to save a post with tags. Whenever I add a new tag to the post, I get this error:
value error at create
My view is this one:
class PostCreateView(CreateView):
template_name = 'posts/create.html'
form_class = PostCreationForm
model = Post
def get_success_url(self):
return reverse('posts:detail', kwargs={"slug": self.object.slug})
def form_valid(self, form):
form.instance.user = self.request.user
form.save() # this is where the error occurs
tags = self.request.POST.get("tag").split(",")
for tag in tags:
current_tag = Tag.objects.filter(slug=slugify(tag))
if current_tag.count() < 1:
create_tag = Tag.objects.create(title=tag)
form.instance.tag.add(create_tag)
else:
existed_tag = Tag.objects.get(slug=slugify(tag))
form.instance.tag.add(existed_tag)
return super(PostCreateView, self).form_valid(form)
The form I'm using is as follow:
class PostCreationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PostCreationForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = "post"
self.helper.field_class = 'form-group'
self.helper.layout = Layout(
Field('title', css_class="form-control", placeholder='Post title'),
Field('content', css_class="form-control", placeholder='Post content'),
Field('category', css_class="form-control"),
Field('image', css_class="form-control"),
Field('tag', css_class="form-control", placeholder='tag1, tag2')
)
self.helper.add_input(Submit('submit', 'Create New Post', css_class='btn btn-underline-primary'))
tag = forms.CharField()
class Meta:
model = Post
fields = ['title', 'content', 'category', 'image', 'tag']
This is the Post model:
class Post(models.Model):
title = models.CharField(max_length=150, unique=True)
content = RichTextUploadingField()
# content = models.TextField()
publish_date = models.DateTimeField(auto_now_add=True)
image = models.ImageField(blank=True, null=True, upload_to='uploads/')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
slug = models.SlugField(default="slug", editable=False)
category = models.ForeignKey(Category, on_delete=models.CASCADE, default=1, related_name='posts')
tag = models.ManyToManyField(Tag, related_name='posts', blank=True)
slider_post = models.BooleanField(default=False)
hit = models.PositiveIntegerField(default=0)
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Post, self).save(*args, **kwargs)
def __str__(self):
return self.title
def post_tag(self):
return ', '.join(str(tag) for tag in self.tag.all())
def comment_count(self):
return self.comments.all().count()
How can I fix this error: "Field 'id' expected a number but got 't'." (where 't' is the first letter from my first tag: 'test'. If I use another tag, then the error display the first letter of that word).
Remove the field tag from fields = ['title', 'content', 'category', 'image', 'tag']
The reason this works is that by including it, Django automatically creates a ModelMultipleChoiceField named tag.
However, since you are manually handling extracting and saving the tags (using tags = self.request.POST.get("tag").split(",")), excluding it from Meta.fields guarantees that Django does not also try to handle this field.
As such, another solution would be to let Django handle the tag form field by completely removing your custom save() method.
I am trying to joint two models in django-rest-framework.
My code isn't throwing any error but also it isn't showing other model fields that need to be joined.
Below is my code snippet:
Serializer:
class CompaniesSerializer(serializers.ModelSerializer):
class Meta:
model = Companies
fields = ('id', 'title', 'category')
class JobhistorySerializer(serializers.ModelSerializer):
companies = CompaniesSerializer(many=True,read_only=True)
class Meta:
model = Jobhistory
fields = ('id', 'title', 'company_id', 'companies')
View .
class UserJobs(generics.ListAPIView):
serializer_class = JobhistorySerializer()
def get_queryset(self):
user_id = self.kwargs['user_id']
data = Jobhistory.objects.filter(user_id=user_id)
return data
model:
class Companies(models.Model):
id = models.AutoField(primary_key=True)
title = models.CharField(max_length=100, blank=True, default='')
category = models.CharField(max_length=30, blank=True, default='')
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created',)
def save(self, *args, **kwargs):
title = self.title or False
category = self.category or False
super(Companies, self).save(*args, **kwargs)
class Jobhistory(models.Model):
id = models.AutoField(primary_key=True)
company_id = models.ForeignKey(Companies)
title = models.CharField(max_length=100, blank=True, default='')
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created',)
def save(self, *args, **kwargs):
company_id = self.company_id or False
title = self.title or False
super(Jobhistory, self).save(*args, **kwargs)
Thanks in advance. Any help will be appreciated.
In your views, you have
serializer_class = JobHistorySerializer()
Remove the parenthesis from this.
The reason for this is apparent in the GenericAPIView, specifically the get_serializer() and get_serializer_class() methods:
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
def get_serializer_class(self):
"""
Return the class to use for the serializer.
Defaults to using `self.serializer_class`.
You may want to override this if you need to provide different
serializations depending on the incoming request.
(Eg. admins get full serialization, others get basic serialization)
"""
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
)
return self.serializer_class
As you can see in get_serializer, it initializes that serializer class with args and kwargs that aren't provided in your view code.
I want to change the rendered field shown in a model form choicefield, based on some user selected feature, which is language in my case.
I've got a two models. Of the two, the 'Vastausvaihtoehto' model saves an answer in both english and finnish, saving it to the database. It also returns the finnish answer by default, because that's how I've defined the unicode function:
Model
class Vastausvaihtoehto(models.Model):
...
vastaus_fi = models.CharField(
verbose_name=_(u'Vastaus'),
max_length=256,
null=True,
blank=True,
)
vastaus_en = models.CharField(
verbose_name=_(u'Vastaus_en'),
max_length=256,
null=True,
blank=True,
)
...
def __unicode__(self):
return u'%s' % (self.vastaus_fi)
class Valinta(models.Model):
organisaatio = models.ForeignKey(
Organisaatio,
related_name=_(u'valinta'),
null=True,
blank=True,
on_delete=models.CASCADE,
)
kysymys = models.ForeignKey(
Kysymysvaihtoehto,
related_name=_(u'valinta'),
null=True,
blank=True,
)
vastausvaihtoehto = models.ForeignKey(
Vastausvaihtoehto,
related_name=_(u'valinta'),
null=True,
blank=True,
)
def __unicode__(self):
return u'%s' % (self.kysymys)
I also have a ModelForm, that I use to select the correct choices
Form
class ValintaForm(ModelForm):
class Meta:
model = Valinta
fields = '__all__'
widgets = {
'organisaatio':forms.HiddenInput(),
'kysymys':forms.HiddenInput(),
'vastausvaihtoehto':forms.RadioSelect(),
}
And here's my view:
View
class kysymys(View):
template_name = 'mytemplate.html'
success_url = 'something'
def get(self, request, pk, question_id, *args, **kwargs):
kysymys = Kysymysvaihtoehto.objects.get(kysymys_id=int(question_id))
vastausvaihtoehdot = Vastausvaihtoehto.objects.filter(kysymysvaihtoehto=kysymys)
if request.LANGUAGE_CODE == 'fi':
# What do I put here?
else:
# What do I put in here?
form = ValintaForm()
form.fields['vastausvaihtoehto'].queryset = vastausvaihtoehdot
form.fields['vastausvaihtoehto'].empty_label = None
return render(request, self.template_name, {
'form':form,
'kysymys':kysymys,
"pk":pk,
"question_id":question_id,
})
I've tried to query just some certain values using values and values_list, and set them as the ModelForm queryset:
#Like so:
answers_en = Vastausvaihtoehto.objects.filter(kysymysvaihtoehto=kysymys).values_list('pk','vastaus_en')
form.fields['vastausvaihtoehto'].queryset = answers_en
But that does not render the form correctly. Should I add a helper method to the 'Vastausvaihtoehto' model, which returns the english name when called?
I know it's possible to circumvent this by just not using ModelForms, but is there a way to do this while using a ModelForm?
Define your ModelForm with an __init__ method which will accept language and question_id as keyword arguments.
class ValintaForm(ModelForm):
class Meta:
model = Valinta
fields = '__all__'
widgets = {
'organisaatio':forms.HiddenInput(),
'kysymys':forms.HiddenInput(),
'vastausvaihtoehto':forms.RadioSelect(),
}
def __init__(self, *args, **kwargs):
language = kwargs.pop('language', None)
question_id = kwargs.pop('question_id')
super(ValintaForm, self).__init__(*args, **kwargs)
if language == "fi":
kysymys = Kysymysvaihtoehto.objects.get(kysymys_id=int(question_id))
vastausvaihtoehdot = Vastausvaihtoehto.objects.filter(kysymysvaihtoehto=kysymys)
self.fields['vastausvaihtoehto'].queryset = vastausvaihtoehdot
else:
# put your other conditions here
pass
In your views, when you initialize your form, pass the keyword arguments
form = ValintaForm(language=request.LANGUAGE_CODE, question_id=question_id)
Or if you think it is better, you can pass the whole queryset to the forms.
def __init__(self, *args, **kwargs):
qs = kwargs.pop('qs')
super(ValintaForm, self).__init__(*args, **kwargs)
self.fields['vastausvaihtoehto'].queryset = qs
Pass the query set when you initialize form
form = ValintaForm(qs=vastausvaihtoehdot)
I would like to get access to fields of the InlineFormSet for consumption into the View.
Here is an example based on https://django-extra-views.readthedocs.io/en/latest/views.html#createwithinlinesview-and-updatewithinlinesview:
from extra_views import InlineFormSet, CreateWithInlinesView,
class ItemsInline(InlineFormSet):
model = Item
class TagsInline(InlineFormSet):
model = Tag
class OrderCreateView(CreateWithInlinesView):
model = Order
inlines = [ItemsInline, TagsInline]
def get_success_url(self):
return self.object.get_absolute_url()
How do I expose the InlineFormSet fields in the get_context_data() and forms_valid() methods?
Warning: Code below fails!
class OrderCreateView(CreateWithInlinesView):
[... see above ...]
def get_context_data(self, **kwargs):
context = super(OrderCreateView, self).get_context_data(**kwargs)
if self.request.POST:
context['items'] = ItemsInline(self.request.POST)
context['tags'] = TagsInline(self.request.POST)
else:
context['items'] = ItemsInline()
context['tags'] = TagsInline()
return context
def forms_valid((self, form, inlines):
[...]
return super(OrderCreateView, self).forms_valid(form, inlines)
I reproduced your example with this models:
STATUS_CHOICES = (
(0, 'Placed'),
(1, 'Charged'),
(2, 'Shipped'),
(3, 'Cancelled'),
)
class Order(models.Model):
name = models.CharField(max_length=255)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
action_on_save = models.BooleanField(default=False)
class Item(models.Model):
item_name = models.CharField(max_length=255)
sku = models.CharField(max_length=13)
price = models.DecimalField(decimal_places=2, max_digits=12, db_index=True)
order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE)
status = models.SmallIntegerField(default=0, choices=STATUS_CHOICES, db_index=True)
date_placed = models.DateField(default=now, null=True, blank=True)
def __unicode__(self):
return '%s (%s)' % (self.item_name, self.sku)
class Tag(models.Model):
tag_name = models.CharField(max_length=255)
content_type = models.ForeignKey(ContentType, null=True, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
return self.tag_name
Urls:
from django.conf.urls import url
from .views import OrderCreateView, OrderUpdateView
urlpatterns = [
url(r'^inlines/new/$', OrderCreateView.as_view()),
url(r'^inlines/(?P<pk>\d+)/$', OrderUpdateView.as_view()),
]
Forms:
from django import forms
from .models import Order, Item
class OrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ['name']
def save(self, commit=True):
instance = super(OrderForm, self).save(commit=commit)
if commit:
instance.action_on_save = True
instance.save()
return instance
class ItemForm(forms.ModelForm):
flag = forms.BooleanField(initial=True)
class Meta:
model = Item
fields = ['item_name', 'sku', 'price', 'order', 'status']
Views:
from django.contrib.contenttypes.models import ContentType
from extra_views import InlineFormSet, CreateWithInlinesView, UpdateWithInlinesView
from extra_views.generic import GenericInlineFormSet
from .forms import OrderForm
from .models import Item, Order, Tag
class ItemsInline(InlineFormSet):
model = Item
fields = ['item_name', 'sku', 'price', 'order', 'status']
class TagsInline(GenericInlineFormSet):
model = Tag
fields = ['tag_name']
class OrderCreateView(CreateWithInlinesView):
model = Order
fields = ['name']
context_object_name = 'order'
inlines = [ItemsInline, TagsInline]
template_name = 'extra_views/order_and_items.html'
def get_success_url(self):
return '/inlines/%i' % self.object.pk
class OrderUpdateView(UpdateWithInlinesView):
model = Order
form_class = OrderForm
inlines = [ItemsInline, TagsInline]
template_name = 'extra_views/order_and_items.html'
def get_success_url(self):
return ''
In OrderCreateView you cannot read Tag and Item records in the
get_context_data method because they haven't been created.
In OrderCreateView you can read all records after the forms_valid
method as usual.
In OrderUpdateView you can read all records in the
get_context_data method like in the forms_valid method.
It looks like this:
class OrderCreateView(CreateWithInlinesView):
# ...
def get_context_data(self, **kwargs):
data = super(OrderCreateView, self).get_context_data(**kwargs)
from pprint import pprint
pprint(self.request.POST) # there is only post data here
return data
def forms_valid(self, form, inlines):
instance = super(OrderCreateView, self).forms_valid(form, inlines)
ct = ContentType.objects.get_for_model(self.model)
print('all items', [item.item_name for item in self.object.items.all()]) # items
print('all tags', [tag.tag_name for tag in
TagsInline.model.objects.filter(content_type=ct, object_id=self.object.id)]) # tags
return instance
class OrderUpdateView(UpdateWithInlinesView):
# ...
def get_context_data(self, **kwargs):
context = super(OrderUpdateView, self).get_context_data(**kwargs)
ct = ContentType.objects.get_for_model(self.model)
print('all items', [item.item_name for item in self.object.items.all()]) # items
print('all tags', [tag.tag_name for tag in
TagsInline.model.objects.filter(content_type=ct, object_id=self.object.id)]) # tags
return context