Django Forms CheckboxInput on Y/N String field in existing database - django

I'm relatively new to Django, and I'm making a form which I'm connecting to a existing MySQL database.
What I'm trying to do is create a maintenance page for countries. One of the fields in my database is eu_member_state.
What I'm trying to do here is create a checkbox for this field, so I can uncheck it for the United Kingdom which recently got out of the EU.
class CountryForm(forms.ModelForm):
class Meta():
model = Countries
fields = '__all__'
widgets = {
'eu_member_state': forms.CheckboxInput()
}
The checkbox does appear on my form, but how can I make this talk to my database? How can this checkbox only be checked when the database value is 'Y', and how do I write this back to my database on save, so 'Y' when checked and 'N' when unchecked?
This is the model code:
from django.db import models
class Countries(models.Model):
country = models.CharField(unique=True, max_length=3)
descr = models.CharField(max_length=50, blank=True, null=True)
descrshort = models.CharField(max_length=20, blank=True, null=True)
country_2char = models.CharField(max_length=2, blank=True, null=True)
eu_member_state = models.CharField(max_length=1, blank=False, null=False, default='N')
class Meta:
db_table = 'countries'
verbose_name_plural = "countries"
def __str__(self):
return self.descr

I managed to work around it by creating a custom CheckboxInput field and initialise this in the forms.py.
class CountryForm(forms.ModelForm):
eu_member_state_flag = forms.BooleanField(required=False,label='EU member state')
class Meta():
model = Countries
exclude = ('eu_member_state',)
def __init__(self, *args, **kwargs):
super(CountryForm, self).__init__(*args, **kwargs)
if self.instance.eu_member_state == "Y":
flag_value = True
else:
flag_value = False
self.initial['eu_member_state_flag'] = flag_value
Then in views.py I'm fetching the value of the CheckboxInput field and convert that into a 'Y' or a 'N'.
class UpdateCountryView(LoginRequiredMixin,UpdateView):
login_url= '/login/'
redirect_field_name = 'tour_admin/countries_list.html'
form_class = CountryForm
model = Countries
def form_valid(self, form):
if form.is_valid():
data = form.cleaned_data
if data['eu_member_state_flag']:
data['eu_member_state'] = 'Y'
self.object.eu_member_state = 'Y'
else:
data['eu_member_state'] = 'N'
self.object.eu_member_state = 'N'
self.object.save()
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('countries_list')

Related

How to display conditional form field that is dependent on an attribute of a selected foreign key on django model form

I want to conditionally display either frequency_input or duration_input fields based on the behavior.recording attribute of the selected behavior.
I have a Trial form that currently displays 3 fields:
behavior_name (foreign Key) dropdown
frequency_input
duration_input
Im not sure if i should the best method to solve this (Javascript or solve in the View)?
Trial Model
class Trial(models.Model):
behavior_name = models.ForeignKey(Behavior, on_delete=models.CASCADE)
client_session = models.ForeignKey(Client_Session, on_delete=models.CASCADE)
frequency_input = models.PositiveIntegerField(default=0, blank=True)
duration_input = models.DurationField(blank=True, default=timedelta(minutes=0))
class Meta:
verbose_name_plural = 'trials'
def __str__(self):
return str(self.id)
Behavior Model
RECORDING_CHOICES = (
('Rate','RATE'),
('Duration','DURATION'),
('Frequency','FREQUENCY')
)
class Behavior(models.Model):
name = models.CharField(max_length=200)
goal = models.CharField(max_length=200)
recording = models.CharField(max_length=10, choices=RECORDING_CHOICES, null=False)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='Active')
def __str__(self):
return self.name
Trial Form
class TrialForm(forms.ModelForm):
class Meta:
model = Trial
fields = ('behavior_name','frequency_input', 'duration_input')
Add Trial View
def add_trial(request, clientsession_id):
client_session = Client_Session.objects.get(id=clientsession_id)
if request.method != 'POST':
form = TrialForm()
else:
form = TrialForm(data=request.POST)
if form.is_valid():
add_trial = form.save(commit=False)
add_trial.client_session = client_session
add_trial.save()
return HttpResponse(status=204, headers={'HX-Trigger': 'trialupdated'})
context = {'client_session': client_session, 'form': form}
return render(request, 'sessions/add_trial.html', context)

How to pass pk of detail view into fk of form

Good afternoon, I am fairly new to Django and I am not sure how to go about this.I have a Django 2.2 project with these models:
class Equipment(models.Model):
name = models.CharField(
max_length=15,
unique=True,
verbose_name='asset name')
asset_cat = models.ForeignKey('category',on_delete=models.PROTECT,verbose_name='asset category')
asset_loc = models.ForeignKey('location',on_delete=models.PROTECT,verbose_name='asset location')
state = models.ForeignKey('status',on_delete=models.PROTECT,verbose_name='status')
brand = models.CharField(
max_length=15,
unique=False,
blank=True)
model = models.CharField(
max_length=12,
unique=False,
blank=True,
verbose_name='model number')
def __str__(self):
return "{}".format(self.name)
def get_absolute_url(self):
return reverse('equipment-detail', args=[str(self.id)])
class Meta:
ordering = ['asset_cat', 'name']
verbose_name_plural = 'pieces of equipment'
class Action(models.Model):
name = models.ForeignKey('equipment',on_delete=models.PROTECT,verbose_name='asset name',blank=False)
dt = models.DateTimeField(
auto_now_add=True,
verbose_name='date and time of incident')
incident = models.TextField(
blank=True,
null=True)
CHANGE = 'CHANGE'
SERVICE = 'SERVICE'
ACTION_CHOICES = (
(CHANGE, 'CHANGE'),
(SERVICE, 'SERVICE')
)
act = models.TextField(
blank=True,
choices=ACTION_CHOICES,
null=True,
verbose_name='action taken')
act_detail = models.TextField(
verbose_name='action detail',
blank=False)
result = models.TextField(
blank=True,
null=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('service-detail', args=[str(self.id)])
class Meta:
ordering = ['-dt']
verbose_name_plural = 'service calls'
I have an Equipment Detail View like this:
class EquipmentDetailView(generic.DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get_context_data(self, **kwargs):
context = super(EquipmentDetailView, self).get_context_data(**kwargs)
return context
The detail view has two buttons: edit and service. If I click edit I have a model form that allows me to edit that instance of the Equipment model successfully.
However, when I click the service button, my form comes up to create an instance of the Action model, but when I submit it tells me that the null value in name_id violates the not null constraint.
It looks like my question is, how can I pass equipment.id from the Equipment Detail view to action.name of the action create form and keep the service button concept?
Action Form:
class ServiceForm(forms.ModelForm):
class Meta:
model = Action
fields = ['incident', 'act_detail', 'result']
Action (actually service) view:
class EquipmentServiceView(generic.CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
Assuming you don't want to go with simpliest solution to include name in form fields and have urls setup as:
/equipment/<id> - equipment detail view
/service - service (or action) create view
There are several ways of passing equipment id:
1) From url
We are going to change url to accept equipment_id. That means instead of /service you will have url /equipment/<equipment_id>/service.
Probably best solution - you will use URL according to REST architecture and will have very clear structure. Client can access page from anywhere (like just copy paste link from mail) and it will work.
urls.py:
urlpatterns = [
path('equipment/<int:pk>', EquipmentDetailView.as_view(), name='equipment-detail'),
path('equipment/<int:equipment_pk>/service', EquipmentServiceView.as_view(), name='service-create')
]
Your service button should look like this: service
and finally your view:
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.kwargs['equipment_pk']
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
2) Session data
In case you want to preserve service url without adding equipment_id, you can store equipment id either in session data(on your server) or in cookies(on client). That's not exactly good - client have to go to EquipmentDetailView prior to creating Service, but this will keep your urls intact.
views.py:
class EquipmentDetailView(DetailView):
model = Equipment
template_name = 'equipment_detail.html'
def get(self, request, *args, **kwargs):
response = super().get(request, *args, **kwargs)
request.session['last_equipment_pk'] = self.object.pk
return response
class EquipmentServiceView(CreateView):
template_name = 'equipment_service.html'
form_class = ServiceForm
queryset = Action.objects.all()
def form_valid(self, form):
equipment_pk = self.request.session.get('last_equipment_pk')
equipment = get_object_or_404(Equipment, pk=equipment_pk)
self.object = form.save(commit=False)
self.object.name = equipment
self.object.save()
return super().form_valid(form)
P.S.: name is bad field name for ForeignField - should be something like equipment or so. Those labels usually associate with CharField and expected to be strings.

'form.is_valid()' fails when I want to save form with choices based on two models

I want to let users to choose their countries. I have 2 models = Countries with some figures and CountriesTranslations. I am trying to make tuple with country (because user has FK to this model) and its translation. In front-end I see dropdown list of countries, but when I try to save the form, I see
error: Exception Value: Cannot assign "'AF'": "UserProfile.country" must be a "Countries" instance.
Error happens at the line if user_profile_form.is_valid():
# admindivisions.models
class Countries(models.Model):
osm_id = models.IntegerField(db_index=True, null=True)
status = models.IntegerField()
population = models.IntegerField(null=True)
iso3166_1 = models.CharField(max_length=2, blank=True)
iso3166_1_a2 = models.CharField(max_length=2, blank=True)
iso3166_1_a3 = models.CharField(max_length=3, blank=True)
class Meta:
db_table = 'admindivisions_countries'
verbose_name = 'Country'
verbose_name_plural = 'Countries'
class CountriesTranslations(models.Model):
common_name = models.CharField(max_length=81, blank=True, db_index=True)
formal_name = models.CharField(max_length=100, blank=True)
country = models.ForeignKey(Countries, on_delete=models.CASCADE, verbose_name='Details of Country')
lang_group = models.ForeignKey(LanguagesGroups, on_delete=models.CASCADE, verbose_name='Language of Country',
null=True)
class Meta:
db_table = 'admindivisions_countries_translations'
verbose_name = 'Country Translation'
verbose_name_plural = 'Countries Translations'
# profiles.forms
class UserProfileForm(forms.ModelForm):
# PREPARE CHOICES
country_choices = ()
lang_group = Languages.objects.get(iso_code='en').group
for country in Countries.objects.filter(status=1):
eng_name = country.countriestranslations_set.filter(lang_group=lang_group).first()
if eng_name:
country_choices += ((country, eng_name.common_name),)
country_choices = sorted(country_choices, key=lambda tup: tup[1])
country = forms.ChoiceField(choices=country_choices, required=False)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
# profiles.views
def profile_settings(request):
if request.method == 'POST':
user_profile_form = UserProfileForm(request.POST, instance=request.user)
if user_profile_form.is_valid():
user_profile_form.save()
messages.success(request, _('Your profile was successfully updated!'))
return redirect('settings')
else:
messages.error(request, _('Please correct the error below.'))
else:
user_profile_form = UserProfileForm(instance=request.user)
return render(request, 'profiles/profiles_settings.html', {
'user_profile_form': user_profile_form,
})
As I understand, country from ((country, eng_name.common_name),) is converted to str. What is the right way to keep country instance in the form? or if I am doing it in the wrong way, what way is correct?
EDITED:
As a possible solution is to use ModelChoiceField with overriding label_from_instance as shown below:
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang='en', *args, **kwargs):
super(CountriesChoiceField, self).__init__(*args, **kwargs)
self.user_lang = user_lang
def label_from_instance(self, obj):
return obj.countriestranslations_set.get(lang_group=self.user_lang)
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(
queryset=Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name'),
widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
but this solution produces too much queries because of the query in label_from_instance and page loads too slowly. Would appreciate any advice.
You probably want to use forms.ModelChoiceField instead of the forms.ChoiceField for your dropdown list.
The ModelChoiceField builds based on a QuerySet and preserves the model instance.
Seems to be solved.
The version below produces 7 queries in 29.45ms vs 73 queries in 92.52ms in the EDITED above. I think it is possible to make it even faster if to set unique_together for some fields.
class CountriesChoiceField(forms.ModelChoiceField):
def __init__(self, user_lang, *args, **kwargs):
queryset = Countries.objects.filter(
status=1, iso3166_1__isnull=False,
countriestranslations__lang_group=user_lang).order_by('countriestranslations__common_name')
super(CountriesChoiceField, self).__init__(queryset, *args, **kwargs)
self.translations = OrderedDict()
for country in queryset:
name = country.countriestranslations_set.get(lang_group=user_lang).common_name
self.translations[country] = name
def label_from_instance(self, obj):
return self.translations[obj]
class UserProfileForm(forms.ModelForm):
user_lang = user_lang_here
country = CountriesChoiceField(widget=forms.Select(), user_lang=user_lang)
class Meta:
model = UserProfile()
fields = ('email', 'email_privacy',
'profile_url',
'first_name', 'last_name',
'country',)
So, now it is possible to have choices based on two (2) models with a good speed. Also the DRY principle is applied, so if there is a need to use choices multiple times in different forms - no problem.

Django Form Validators on Comparing One Object Against Another

I am creating a Non Disclosure Agreement form that a user fills out after registering and logging in. I am using a custom signup form with AllAuth and pre-populating parts of the form. I pre-populate the first and last name into the top part of the form as shown below in first screen shot, but as a part of the digital signature I am setting up; I need to validate the typed signature field matches the name of the first_name and the last_name concatenated together per the second screen-shot. I know I need to setup a validator based on Django Form & Field Validations and I've tried several things but just can't get my mind rapped around it. Any help putting this together would be huge...thank you.
My Models
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="profile", verbose_name="user")
...
class NonDisclosure(Timestamp):
profile = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="nda", verbose_name="profile")
user_signature = models.CharField(max_length=250, verbose_name='Signature')
user_street = models.CharField(max_length=250, verbose_name='Street Address')
user_city = models.CharField(max_length=250, verbose_name='City')
user_state = models.CharField(max_length=2, verbose_name='State Initials')
user_zip = models.IntegerField(verbose_name='Zip Code')
phone = models.CharField(max_length=25, verbose_name='Phone Number')
cash_on_hand = models.CharField(max_length=250, verbose_name='Cash on Hand')
value_of_securities = models.CharField(max_length=250, verbose_name='Value of Securities')
equity_in_real_estate = models.CharField(max_length=250, verbose_name='Equity on Real Estate')
other = models.CharField(max_length=250, verbose_name='Other Assets')
#property
def username(self):
return self.profile.username
#property
def first_name(self):
return self.profile.first_name
#property
def last_name(self):
return self.profile.last_name
#property
def email(self):
return self.profile.email
class Meta:
verbose_name = 'Non Disclosure Agreement'
verbose_name_plural = 'Non Disclosure Agreements'
def __str__(self):
return "%s" % self.profile
def get_absolute_url(self):
return reverse('nda_detail', args=[str(self.id)])
My Views:
class NonDisclosureForm(BaseModelForm):
cash_on_hand = forms.CharField(required=False)
value_of_securities = forms.CharField(required=False)
equity_in_real_estate = forms.CharField(required=False)
other = forms.CharField(required=False)
class Meta:
model = NonDisclosure
fields = ['user_signature', 'user_street', 'user_city', 'user_state', 'user_zip', 'phone', 'cash_on_hand', 'value_of_securities', 'equity_in_real_estate', 'other']
class NdaCreate(CreateView):
form_class = NonDisclosureForm
template_name = 'nda/nda_form.html'
def form_valid(self, form):
form.instance.profile = Profile.objects.get(user=self.request.user)
form.instance.created_by = self.request.user
return super(NdaCreate, self).form_valid(form)
Firstly, you should subclass ModelForm, not BaseModelForm. Write a clean_<fieldname> method for your user_signature field, and make sure that the value is as expected. You can access self.instance.created_by to check.
class NonDisclosureForm(ModelForm):
...
class Meta:
model = NonDisclosure
fields = ['user_signature', ...]
def clean_user_signature(self):
user_signature = self.cleaned_data['user_signature']
expected_name = '%s %s' % (self.instance.created_by.first_name, self.instance.created_by.last_name)
if user_signature != expected_name:
raise forms.ValidationError('Signature does not match')
return user_signature
Then you need to update your view so that it sets instance.created_by. You can do this by overriding get_form_kwargs.
class NdaCreate(CreateView):
def get_form_kwargs(self):
kwargs = super(NdaCreate, self).get_form_kwargs()
kwargs['instance'] = NonDisclosure(created_by=self.request.user)
return kwargs

Django formview returns object already exists error with a modelform

I'm trying to create a FormView that receives a string but it gives me a "objects already exists" error when I complete the field I give. What I'm trying to do is to create a view that checks if a certain "product" (model) exists, if that product really exists, redirect to another view based on the product "pk" to create another model.
Basically the course of action is like this:
Check if product exists.
if exists redirect to create order (model) view, else no nothing.
Fill the create order form, if valid, create the order and assign the product fk relation to order.
Here's my code
views.py
class BuyOrderCheckProduct(generic.FormView):
template_name = 'buy_order/buy_order_check_product.html'
form_class = forms.CheckProductForm
def form_valid(self, form):
try:
product = Product.objects.get(codename=form.cleaned_data['codename'])
except Product.DoesNotExist:
product = None
if product:
# Never enters here because correct existing codename gives form_invalid, don't know why
return super(BuyOrderCheckProduct, self).form_valid()
else:
# It only enters when I input a non-existent codename for product
return super(BuyOrderCheckProduct, self).form_invalid()
def form_invalid(self, form):
# I don't know why it enters here!
return super(BuyOrderCheckProduct, self).form_invalid()
def get_success_url(self, **kwargs):
# TODO: How to pass product pk as kwargs?
return reverse_lazy('order_create', self.kwargs['pk'])
class BuyOrderCreate(generic.CreateView):
template_name = 'buy_order/buy_order_create.html'
form_class = forms.BuyOrderCreateForm
success_url = reverse_lazy('buy_order_list')
# TODO: Need to create a custom form_valid to add product fk to order.
forms.py
class CheckProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ['codename']
class BuyOrderCreateForm(forms.ModelForm):
class Meta:
model = BuyOrder
models.py
"""
ORDER
"""
class Order(models.Model):
class Meta:
verbose_name = u'orden'
verbose_name_plural = u'ordenes'
abstract = True
unit_price = models.IntegerField(u"precio unitario", )
quantity = models.IntegerField(u"cantidad", default=1)
discount = models.IntegerField(u"descuento")
def __unicode__(self):
return self.code
class BuyOrder(Order):
class Meta:
verbose_name = u'orden de compra'
verbose_name_plural = u'ordenes de compra'
product = models.ForeignKey(Product, related_name="buy_orders", editable = False)
bill = models.ForeignKey(BuyBill, related_name="orders", null=True, editable = False)
"""
PRODUCT
"""
class Product(models.Model):
class Meta:
verbose_name = u'producto'
verbose_name_plural = u'productos'
category = models.ForeignKey(Category, verbose_name=u'categoría', related_name='products')
codename = models.CharField(u"código", max_length=100, unique=True)
name = models.CharField(u"nombre", max_length=100)
description = models.TextField(u"descripción", max_length=140, blank=True)
sale_price = models.IntegerField(u"precio de venta", default=0)
purchase_price = models.IntegerField(u"precio de compra", default=0)
profit = models.IntegerField(u"lucro", default=0)
profit_margin = models.IntegerField(u"margen de lucro", default=0)
tax = models.IntegerField(u"tasa", default=0)
quantity = models.IntegerField(u"cantidad", default=0)
picture = models.ImageField(u"imagen", upload_to='product_pictures', blank=True)
group = models.ForeignKey(Group, verbose_name=u'grupo', related_name='products')
def __unicode__(self):
return self.name
I'll be appreciated if you give me a tip for creating a correct get_success_url() for this case.
Ok. I found a solution for my error. What caused the model already exists error was my ModelForm CheckProductForm. Codename attribute is unique, so my validation always returned False. What I did was to change my orginal ModelForm to a Form. This solved my whole issue. And for the form_invalid in form_valid issue. I've overwritten my form's clean_codename function to raise ValidationError if product doesn´t exist.
Here's the solution I found:
views.py
class BuyOrderCheckProduct(generic.FormView):
template_name = 'buy_order/buy_order_check_product.html'
form_class = forms.CheckProductForm
def form_valid(self, form):
product = Product.objects.get(codename=form.cleaned_data['codename'])
return redirect('buy_order_create', pk=product.pk)
forms.py
class CheckProductForm(forms.Form):
codename = forms.CharField(label=u'código')
def clean_codename(self):
try:
product = Product.objects.get(codename=self.cleaned_data['codename'])
except Product.DoesNotExist:
raise forms.ValidationError("This codename doesn't exist.")
return product
PD: Sorry for the dumb questions.