Django model conditional default value based on other field - django

There is two tables Company and Template_message
each company can have desired template messages for each message
messages have 3 types ( invitation,rejection,Job_offer)
now i want to set a default text for each template_message which the company can change it later on
but i dont know how to set the default value for each template message , based on their type
the model i designed is bellow :
class TemplateMessage(models.Model):
TEMPLATE_TYPE_CHOICES = (
('1', 'invitation'),
('2', 'Rejecting'),
('3', 'Job_offer_acceptance'),
)
type = models.CharField(max_length=4, choices=TEMPLATE_TYPE_CHOICES, default='1')
def subject_initials(type):
match type:
case 1:
return "[jobName] skills test invitation from [companyName]"
case 2:
return "Thank you from [companyName]"
case 3:
return "Job offer letter from [companyName]"
subject = models.CharField(max_length=256, default=subject_initials(type))
company = models.ForeignKey('Company', on_delete=models.CASCADE)
class Meta:
unique_together = ("company", "type")
def __str__(self):
return self.type
but it does not not work and when i go to admin panel the subject text is not set to any default value

you should override ModelAdmin.get_changeform_initial_data method for your ModelAdmin.
class MyModelAdmin(ModelAdmin):
... # any staff before
def get_changeform_initial_data(self, request):
initial_data = super().get_changeform_initial_data(request)
initial_data['subject'] = subject_initials(get_type_from(request))
return initial_data
... # any staff after
Other possibility_ override ModelAdmin.changeform, to add in request.GET['subject'] = your_initial_subject.
class MyModelAdmin(ModelAdmin):
... # any staff before
def changeform_view(self, request, *args, **kwargs):
request.GET._mutable = True
request.GET['subject'] = your_initial_subject
return super().changeform_view(request, *args, **kwargs)
... # any staff after
But changing the request.GET/POST internal is a bad paractice.

Related

Limit choices inside an input django

I am currently trying to improve my form by restricting choices inside an input (user can choose only their own tags).
tag name / user name
I tried to do that inside the get/post function :
def post(self, request, *args, **kwargs):
form = DateInputForm(request.POST, limit_choices_to={'tags__user': request.user})
def get(self, request, *args, **kwargs):
form = DateInputForm(limit_choices_to={'tags__user': request.user})
(1) I get an error.
BaseModelForm.init() got an unexpected keyword argument 'limit_choices_to'
My form :
class DateInputForm(ModelForm):
class Meta:
model = Task
# exclude = ('user',)
fields = ['user', 'title', 'description', 'date_to_do', 'complete', 'tags']
widgets = {
'date_to_do': forms.DateTimeInput(format='%Y-%m-%dT%H:%M',
attrs={'type': 'datetime-local', 'class': 'timepicker'}),
}
My view :
class TaskUpdate(LoginRequiredMixin, UpdateView):
model = Task
template_name = "tasks/task_form.html"
form_class = DateInputForm
Tag model :
class Tag(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
tag_name = models.CharField(max_length=200, null=True, blank=False, default='')
Globally: the goal is to limit the tags that can be chosen by the user in my task form (with the tag input); currently, a user can choose another user's tag, which is not what I want.
I think the easiest way to do this is to override the constructor of your form, as shown in this answer: https://stackoverflow.com/a/1969081/18728725
Update:
Here is Wamz's solution:
def __init__(self, *args, **kwargs):
super(DateInputForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
new_kwargs = kwargs.get('instance')
self.fields['tags'].queryset = Tag.objects.filter(user=new_kwargs.user.id)
Warning, that's not working in the createView

Use same serializer class but with different action in a view in Django rest framework

I have a modelset view in which different customs functions are defined based on the requirement. I have to write another get function in which I want to use the same serializer class. But the field which I have defined in the serializer class in pkfield but for the get function, I want it as a stringfield rather than pk field. How to achieve that??
Also, I have defined depth=1, which is also not working.
class Class(TimeStampAbstractModel):
teacher = models.ForeignKey(
Teacher,
on_delete=models.CASCADE,
null=True,
related_name="online_class",
)
subject = models.ForeignKey(
Subject,
on_delete=models.SET_NULL,
null= True,
related_name= "online_class",
)
students_in_class = models.ManyToManyField(Student, related_name="online_class")
My view:
class ClassView(viewsets.ModelViewSet):
queryset = Class.objects.all()
serializer_class = ClassSerializer
serializer_action_classes = {
'add_remove_students': AddStudentstoClassSerializer,
'get_all_students_of_a_class': AddStudentstoClassSerializer,
}
def get_serializer_class(self):
"""
returns a serializer class based on the action
that has been defined.
"""
try:
return self.serializer_action_classes[self.action]
except (KeyError, AttributeError):
return super(ClassView, self).get_serializer_class()
def add_remove_students(self, request, *args, **kwargs):
"""
serializer class used is AddStudentstoClassSerializer
"""
def get_all_students_of_a_class(self,request,pk=None):
"""
for this I function too, I want to use the same AddStudentstoClassSerializer class but
there is a problem. The field students_in_class is already defined as pkfield, whereas I
want to use it as a stringfields in the response of this function
""""
My serializer:
class AddStudentstoClassSerializer(serializers.ModelSerializer):
students_in_class = serializers.PrimaryKeyRelatedField(
many=True, queryset=Student.objects.all()
)
class Meta:
model = Class
fields = ["students_in_class"]
depth = 1
def update(self, instance, validated_data):
slug = self.context["slug"]
stu = validated_data.pop("students_in_class")
/................other codes....../
return instance
Here we can see the student_in_class is defined as pkfield which is ok when using the update api, but when I want to use the get api and call get_all_students_of_a_class I want the field to be stringfield or some other field. How to do that? Also depth= 1 is also not working.
Update:
Treid the following but still not working:
def to_representation(self, instance):
rep = super().to_representation(instance)
# rep["students_in_class"] = instance.students_in_class
rep['students_in_class'] = StudentSerializer(instance.students_in_class).data
return rep
class StudentSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Student
fields = ['user', 'college_name', 'address']
what i got in the response is
{
"students_in_class": {}
}
it is empty dict. what should be done!
You can override you to_representation method like this.
class AddStudentstoClassSerializer(serializers.ModelSerializer):
students_in_class = serializers.PrimaryKeyRelatedField(
many=True, queryset=Student.objects.all()
)
class Meta:
model = Class
fields = ["students_in_class"]
def to_representation(self, instance):
data = {
"students_in_class": # Write your logic here
}
return data
def update(self, instance, validated_data):
slug = self.context["slug"]
stu = validated_data.pop("students_in_class")
/................other codes....../
return instance

How can I disable the rendering of a form input field in django form

I've got the following Form. I let Django render the form automatically for me in my template with: {{ form.as_p() }}. As you can see I've got the company field, however, it's redundant as I am setting the company by clean_company. The company field is hidden, but I want it to be completely gone in the template. I still need it in the form though, because I want to be able to call: form.save(commit=True).
Is there a way to get the hidden field out of my template?
class PlantPurchaseForm(forms.ModelForm):
company = forms.CharField(initial="", widget=forms.HiddenInput())
number = forms.IntegerField(initial=10000, min_value=10000, max_value=99999)
date = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}))
class Meta:
model = PlantPurchase
fields = (
"company",
"number",
"date",
"plant",
"costs",
)
def __init__(self, company, *args, **kwargs):
self.company = company
super(PlantPurchaseForm, self).__init__(*args, **kwargs)
def clean_company(self):
data = self.cleaned_data["company"]
data = self.company
return data
def clean_date(self):
data = self.cleaned_data["date"]
data = datetime.combine(data, time(hour=12))
data = pytz.utc.localize(data)
return data
The PlantPurchase Model:
class PlantPurchase(models.Model):
company = models.ForeignKey(Company, related_name="plant_purchases")
number = models.PositiveSmallIntegerField(unique=True, validators=[MinValueValidator(10000),
MaxValueValidator(99999)])
date = models.DateTimeField()
plant = models.ForeignKey(Plant, related_name="plant_purchase", on_delete=models.PROTECT)
costs = models.DecimalField(max_digits=8, decimal_places=2)
class Meta:
unique_together = ("company", "number")
def __str__(self):
text = "Purchase: #{} {}".format(self.number, self.plant)
return text
I solved my problem by accessing the form instance in the init method. This way I could remove the company field and it won't be rendered in the template anymore.
class PlantPurchaseForm(forms.ModelForm):
number = forms.IntegerField(initial=10000, min_value=10000, max_value=99999)
date = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}))
class Meta:
model = PlantPurchase
fields = (
"number",
"date",
"plant",
"costs",
)
def __init__(self, company, *args, **kwargs):
super(PlantPurchaseForm, self).__init__(*args, **kwargs)
self.instance.company = company
def clean_date(self):
data = self.cleaned_data["date"]
data = datetime.combine(data, time(hour=12))
data = pytz.utc.localize(data)
return data
There are several ways how to do it.
Basically, if you are sure that hidden field is not in use then you can remove it from the form with exlude. You can remove init method as well.
class PlantPurchaseForm(forms.ModelForm):
number = forms.IntegerField(initial=10000, min_value=10000, max_value=99999)
date = forms.DateField(widget=forms.DateInput(attrs={"type": "date"}))
class Meta:
model = PlantPurchase
fields = [
'company',
'number',
'date',
'plant',
'costs',
]
exclude = ["company"]
After that you need to save company into the models instance. You can either save it after form is validated:
def post(self, request):
...
if form.is_valid():
plantPurchase = form.save()
plantPurchase.company = company
plantPurchase.save()
...
..or pass the company into the save method of the form:
def save(self, company, force_insert=False, force_update=False, commit=False):
purchase = super(PlantPurchaseForm, self).save(commit=commit)
purchase.company = company
purchase.save()
return purchase

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

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)

Make a field required if another field is checked in Django form

I have a model like this:
class News(models.Model):
is_activity = models.BooleanField(default=False)
activity_name = models.CharField(max_length=240, blank=True, null=True)
What I am trying to achieve is, if is_activity is checked in I want activity_name to be required. Thus, I am trying to override the __init__ method:
class NewsForm(forms.ModelForm):
class Meta:
model = News
def __init__(self, *args, **kwargs):
super(NewsForm, self).__init__(*args, **kwargs)
if self.fields['is_activity'] is True:
self.fields['activity_name'].required = True
class NewsAdmin(FrontendEditableAdminMixin, admin.ModelAdmin):
form = NewsForm
Even if I check in the is_activity the activity_name is non-required. What's wrong?
The ModelForm.clean() method gives you access to the cleaned data – this is where you can include the field-specific conditional logic:
from django.core.validators import EMPTY_VALUES
class NewsForm(forms.ModelForm):
class Meta:
model = News
def clean(self):
is_activity = self.cleaned_data.get('is_activity', False)
if is_activity:
# validate the activity name
activity_name = self.cleaned_data.get('activity_name', None)
if activity_name in EMPTY_VALUES:
self._errors['activity_name'] = self.error_class([
'Activity message required here'])
return self.cleaned_data
class NewsAdmin(FrontendEditableAdminMixin, admin.ModelAdmin):
form = NewsForm