How to change the rendered field in Django's ModelForm queryset? - django

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)

Related

How to filter queryset value in one of the form fields using CreateView?

My task is to change the value of one field in the form (drop-down list with Foreignkey connection). I need to exclude the values of technology that the user already has.
I use CreateView and ModelForm.
forms.py
class SkillCreateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SkillCreateForm, self).__init__(*args, **kwargs)
employee_current_technology = Technology.objects.filter(??? --- How can I get editing user pk ????-----)
self.fields['technology'].queryset = Technology.objects.exclude(name__in=employee_current_technology)
I know that somehow I can get pk from url using kwarg and get_form_kwarg values, but I can't figure out how to do that.
urls.py
path('profile/<int:pk>/skill/create/', SkillCreateView.as_view(), name='skill_create'),
views.py
class SkillCreateView(AuthorizedMixin, CreateView):
"""
Create new course instances
"""
model = Skill
form_class = SkillCreateForm
template_name = 'employee_info_create.html'
def get_form_kwargs(self):
kwargs = super(SkillCreateView, self).get_form_kwargs()
Employee.objects.get(pk=self.kwargs['pk']) -->get me pk
????
return kwargs
.....
models.py
class Employee(models.Model):
"""Employee information."""
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='employee')
summary = models.TextField("summary", blank=True, default='')
skills = models.ManyToManyField(
Technology, through="Skill", verbose_name="skills", blank=True)
class Skill(models.Model):
"""Information about an employee's skills."""
employee = models.ForeignKey(
Employee, on_delete=models.CASCADE, related_name="employee_skills")
technology = models.ForeignKey(Technology, on_delete=models.CASCADE)
class Technology(models.Model):
"""Technologies."""
tech_set = models.ForeignKey(Skillset, on_delete=models.CASCADE, related_name="skillset")
name = models.CharField('technology name', max_length=32, unique=True)
group = models.ForeignKey(Techgroup, on_delete=models.CASCADE, related_name="group")
You can inject the pk in the form, like:
class SkillCreateView(AuthorizedMixin, CreateView):
"""
Create new course instances
"""
model = Skill
form_class = SkillCreateForm
template_name = 'employee_info_create.html'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update(employee_pk=self.kwargs['pk'])
return kwargs
You can then update the queryset in the form like:
class SkillCreateForm(forms.ModelForm):
def __init__(self, *args, employee_pk=None, **kwargs):
super().__init__(*args, **kwargs)
if employee_pk is not None:
self.fields['technology'].queryset = Technology.objects.exclude(
skill__employee_id=employee_pk
)

How to join models in Python djangorestframework

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.

Django Rest Framework - display prepopulated slug field

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')

Filter select field in ModelForm by currently logged in user

I'm trying to display a form (ModelForm) with a select field filtered by currently logged in user. The select field in this case contains a list of categories. I want to display only the categories which "belong" to the currently logged in user. The category field is a foreign key to the IngredienceCategory model.
Here is what I've come up with so far but it's giving me an error (unexpected keyword queryset). Any ideas what I'm doing wrong?
# models.py
class IngredienceCategory(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredience Categories"
def __unicode__(self):
return self.name
class Ingredience(models.Model):
name = models.CharField(max_length=30)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(IngredienceCategory, null=True, blank=True)
class Meta:
verbose_name_plural = "Ingredients"
def __unicode__(self):
return self.name
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
# views.py
def home(request):
if request.user.is_authenticated():
username = request.user.username
email = request.user.email
foods = Food.objects.filter(user=request.user).order_by('name')
ingredients = Ingredience.objects.filter(user=request.user).order_by('name')
ingrcat = IngredienceCategory.objects.filter(user=request.user)
if request.method == 'POST':
form = IngredienceForm(request.POST)
if form.is_valid():
# Create an instance of Ingredience without saving to the database
ingredience = form.save(commit=False)
ingredience.user = request.user
ingredience.save()
else:
# How to display form with 'category' select list filtered by current user?
form = IngredienceForm(queryset=IngredienceCategory.objects.filter(user=request.user))
context = {}
for i in ingredients:
context[i.category.name.lower()] = context.get(i.category.name.lower(), []) + [i]
context2 = {'username': username, 'email': email, 'foods': foods, 'ingrcat': ingrcat, 'form': form,}
context = dict(context.items() + context2.items())
else:
context = {}
return render_to_response('home.html', context, context_instance=RequestContext(request))
That's happening because ModelForm does not take a queryset keyword.
You can probably achieve this by setting the queryset on the view:
form = IngredienceForm()
form.fields["category"].queryset =
IngredienceCategory.objects.filter(user=request.user)
See related question here.
Here i have another suggestion to solve the problem. You can pass request object in your form object inside view.
In view.py just pass the request object.
form = IngredienceForm(request)
In your forms.py __init__ function also add request object
from models import IngredienceCategory as IC
class IngredienceForm(ModelForm):
class Meta:
model = Ingredience
fields = ('name', 'category')
def __init__(self, request, *args, **kwargs):
super(IngredienceForm, self).__init__(*args, **kwargs)
self.fields['name'].queryset = IC.objects.filter(user=request.user)
This filter always will be applied whenever you initialize your form .

saving django ManyToMany not valid

I have a form from my model that needs to be validated and saved making use of ManyToMany Fields.
Everytime I try and save it, I get thrown back to the page, just saying this field is required
My models.py
class HuntingReport(models.Model):
user = models.ForeignKey(User, related_name='User')
outfitter = models.ForeignKey(User, related_name='Outfitter', null=True, blank=True)
date_travel_started = models.DateField(blank=True, null=True)
date_travel_ended = models.DateField(null=True, blank=True)
title = models.CharField(max_length=50)
report = models.TextField()
wish_list = models.ManyToManyField(Specie)
bag_list = models.ManyToManyField(Trophies)
def __unicode__(self):
return self.title
My forms.py looks as follows
class HuntingReportForm(ModelForm):
date_travel_started = forms.DateField(widget=extras.SelectDateWidget(years=range(1970,2010)))
date_travel_ended = forms.DateField(widget=extras.SelectDateWidget(years=range(1970,2010)))
wish_list = forms.ModelMultipleChoiceField(queryset=Specie.objects.all(), widget=FilteredSelectMultiple("verbose name", is_stacked=False))
bag_list = forms.ModelMultipleChoiceField(queryset=Trophies.objects.all(), widget=FilteredSelectMultiple("verbose name", is_stacked=False))
class Meta:
model = HuntingReport
exclude = ['user']
def __init__(self, user, *args, **kwargs):
super(HuntingReportForm, self).__init__(*args, **kwargs)
users = User.objects.filter(userprofile__outfitter=True)
self.fields['outfitter'].choices = [('', '')] + [(user.pk, user.get_full_name()) for user in users]
my views.py
def create(request, template_name='reports/new.html'):
if request.method == 'POST':
form = HuntingReportForm(request.POST, request.FILES)
if form.is_valid():
newform = form.save(commit=False)
newform.user = request.user
newform.save_m2m()
return HttpResponseRedirect('/hunting-reports/')
else:
form = HuntingReportForm(request.user)
context = { 'form':form, }
return render_to_response(template_name, context,
context_instance=RequestContext(request))
Did you try passing blank=True for model field's constructor, or required=False for the ModelMultipleChoiceField's constructor?
I know that blank=True solves the problem for the form in the admin panel, but I don't know how it gets mapped to the ModelForm's fields. I'm assuming that it gets mapped to required property.