overriding validate_unique not raising a ValidationError - django

I am trying to override the validate_unique for on of my models to validate that a record doesn't exist with duplicate fields (labeler, date), but It's not raising any validation error when i enter duplicate (labeler, date).
I've already looked through the issues here and here and constructed my view and model as follows:
my view:
class AttendanceCreateView(CreateView):
model = Attendance
template_name = "attendance/attendance_form.html"
fields = [
"team_lead",
"labeler",
"attendance",
]
def get_success_url(self):
return reverse("attendance-home")
my model:
class Attendance(models.Model):
id= models.AutoField(primary_key=True)
attendance = models.IntegerField(blank=True, choices= CHOICES_ATTENDANCE, null=True)
labeler = models.ForeignKey('Labelers', models.CASCADE, blank=True, null=True)
team_lead = models.ForeignKey('TeamLeads', models.CASCADE, blank=True, null=True)
date = models.DateField(auto_now_add=True, null=True)
class Meta:
managed = False
db_table = "attendance"
def validate_unique(self, *args, **kwargs):
super().validate_unique(*args, **kwargs)
if self.__class__.objects.filter(labeler= self.labeler, date= self.date).exists():
raise ValidationError(message='a already record exists for date and labeler',)
any idea why my code is not behaving as expected ?
thanks is advance.

Related

Autofilling Django model form field with data from associated objects

I have a model form that creates a new job entry, and on submission, I need an invisible field job_time_estimation to be set to a sum of 'service_stats_estimate_duration' values from ServiceItemStats objects associated with the JobEntry by a many-to-many relationship when submitting the form.
For example, if in my NewJobEntryForm I chose two existing ServiceItemStats objects that have service_stats_estimate_duration values 60 and 90, on submission, I want a value 150 to be saved in that JobEntry object's job_time_estimation attribute.
I tried doing this using aggregation by defining a save() method in the model but I am getting an error "name 'serviceItemStats' is not defined".
I am not sure if I am going about this the right way. Any help would be appreciated.
My code:
models.py:
class ServiceItemStats(models.Model):
service_stats_name = models.CharField(primary_key=True, max_length=20)
service_stats_estimate_duration = models.IntegerField()
# Many-to-many relationship with JobEntry.
def __str__(self):
return self.service_stats_name
class JobEntry(models.Model):
# PK: id - automatically assigned by Django.
jo
b_entry_date_time = models.DateTimeField(default=timezone.now)
jo
b_date = models.DateField(blank=True, null=True)
job_checked_in = models.BooleanField()
job_checked_out = models.BooleanField(default=False)
job_priority = models.IntegerField()
job_time_estimation = models.IntegerField(blank=True, null=True)
job_comments = models.TextField(max_length=200, blank=True, null=True)
job_parts_instock = models.BooleanField(default=False)
job_started = models.BooleanField(default=False)
job_finished = models.BooleanField(default=False)
job_expand_fault_evidence = models.ImageField(blank=True, null=True)
job_expand_comments = models.ImageField(blank=True, null=True)
job_expand_parts_required = models.CharField(max_length=200, blank=True, null=True)
vehicle = models.ForeignKey(Vehicle, on_delete=models.CASCADE) #One-to-one relationship
customer = models.ForeignKey(Customer, on_delete=models.CASCADE) #One-to-one relationship
serviceBay = models.ForeignKey(ServiceBay, on_delete=models.CASCADE, blank=True, null=True) #One-to-one relationship
serviceItemStats = models.ManyToManyField(ServiceItemStats, blank=True) #Many-to-many relationship
def __str__(self):
return self.id
def save(self, *args, **kwargs):
if not self.job_time_estimation:
self.job_time_estimation = serviceItemStats.objects.all().aggregate('service_stats_estimate_duration')
return super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse("jobs:job_detail",kwargs={'pk':self.pk})
views.py
class JobCreateView(FormView):
template_name = "jobs/jobentry_form.html"
form_class = NewJobEntryForm
success_url = reverse_lazy("jobs:job_list")
def form_valid(self, form):
form.save()
return super(job_list, self).form_valid(form)
forms.py
class NewJobEntryForm(ModelForm):
class Meta:
model = JobEntry
fields = ['vehicle', 'customer', 'job_date', 'job_checked_in', 'job_priority', 'job_comments', 'job_parts_instock', 'serviceItemStats']
widgets = {
'job_date' : forms.DateInput(format=('%m/%d/%Y'), attrs={'class':'form-control', 'placeholder':'Select a date', 'type':'date'}),
'ServiceItemStats' : forms.CheckboxSelectMultiple(),
'job_priority' : forms.RadioSelect(choices=priorityOptions),
}
You can try this.
from django.db.models import Sum
class JobCreateView(FormView):
template_name = "jobs/jobentry_form.html"
form_class = NewJobEntryForm
success_url = reverse_lazy("jobs:job_list")
def form_valid(self, form):
job=form.save()
estimation = job.serviceItemStats.all().aggregate(total=Sum('service_stats_estimate_duration'))
job.job_time_estimation = estimation['total']
job.save()
return super(job_list, self).form_valid(form)

Django ModelChoiceField: filtering object based on pk in url

I've read many questions about this topic, but none of the methods work for me.
There are 3 related models:
class Trips(models.Model):
lake = models.CharField("Lake", max_length=150)
city = models.CharField("City", max_length=100, blank=True)
s_date = models.DateTimeField("Starting Date", auto_now=False, auto_now_add=False)
e_date = models.DateTimeField("Ending Date", auto_now=False, auto_now_add=False)
trip_id = models.AutoField(primary_key=True)
class Meta:
verbose_name = "Trip"
verbose_name_plural = "Trips"
def __str__(self):
return f"{self.lake}-{self.trip_id}-{self.s_date}"
class Fisherman(models.Model):
name = models.CharField("Fisherman", max_length=50)
trip = models.ForeignKey(Trips, on_delete=models.CASCADE)
fisherman_id = models.AutoField(primary_key=True)
class Meta:
verbose_name = "Fisherman"
verbose_name_plural = "Fishermen"
def __str__(self):
return f"{self.name}-{self.fisherman_id}"
class Catch(models.Model):
fish_type = models.CharField("Fish Type", max_length=50)
catch_id = models.AutoField(primary_key=True)
weight = models.DecimalField("Weight", max_digits=5, decimal_places=2)
length = models.DecimalField("Length", max_digits=5, decimal_places=2, blank=True, null=True)
datetime = models.DateTimeField("Catch Time", auto_now=False, auto_now_add=False)
fisherman = models.ForeignKey(Fisherman, on_delete=models.CASCADE)
trip = models.ForeignKey(Trips, on_delete=models.CASCADE)
class Meta:
verbose_name = "Catch"
verbose_name_plural = "Catches"
def __str__(self):
return f"{self.fish_type}-{self.catch_id}"
I have a ModelForm to create a new catch. Here I use a ModelChoiceField to list Fishermen, but I don't know how to filter them. I only want display those who belong to the trip.
class CatchForm(forms.ModelForm):
fisherman = forms.ModelChoiceField(queryset= Fisherman.objects.all())
class Meta:
model = Catch
fields = ["fish_type", "weight", "length", "datetime", "fisherman"]
widgets = {
"datetime": forms.DateTimeInput(format='%Y-%m-%d %H:%M', attrs={'class':'datetimefield form-control'}),
}
views.py
I' ve read that get_form_kwargs should be used in views to override fields in the form, but it didn't work for me.
class NewCatchView(CreateView):
model = Catch
form_class = CatchForm
template_name = "new_trip/new_catch.html"
# Probably, this is wrong
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['fisherman'] = Fisherman.objects.filter(trip=self.kwargs.get('pk'))
return kwargs
def form_valid(self, form):
form.instance.trip = Trips.objects.get(pk=self.kwargs['pk'])
return super().form_valid(form)
def get_success_url(self):
return reverse('new_trip:trip_details', args=(self.kwargs['pk'],))
urls.py
path("trip_details/<int:pk>/new_catch/", views.NewCatchView.as_view(), name="new_catch"),
Thank you in advance for your help!
You're almost there. You've created the kwarg, so now you just need to use it in the form to overwrite the original queryset:
class CatchForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
fisherman = kwargs.pop('fisherman')
super().__init__(*args, **kwargs)
self.fields['fisherman'].queryset = fisherman

Custom "business" logic using pure Django

I am set on building a small website using django. What i am trying to do right now is using a CreateView based on a Model "Order", which one of its fields is another model "Customer".
The form itself works to create Orders, but im trying to find out how i can validate that the Customer that was selected is "enabled" (there is a status in the Customer model).
I was trying using the clean method but it doesnt even seem to be executing. I tried just raising the error on the clean, without validating anything, and still doesnt work. Any clue what might be wrong?
My Form:
class OrderForm(ModelForm):
def clean_customer(self, *args, **kwargs):
raise forms.ValidationError("This customer is banned.")
class Meta:
model = Order
fields = '__all__'
Relevant Models:
class Order(models.Model):
ORDER_STATUS = (('Pending Delivery', 'Pending Delivery'),
('Book on Customer', 'Book on Customer'),
('Overdue', 'Overdue'),
('Completed','Completed'))
customer = models.ForeignKey(Customer, null=True, on_delete=models.SET_NULL)
book = models.ForeignKey(Book, null=True, on_delete=models.SET_NULL)
date_created = models.DateTimeField(auto_now_add=True)
date_due = models.DateTimeField(null=True)
date_completed = models.DateTimeField(null=True, blank=True)
status = models.CharField(max_length=100, choices=ORDER_STATUS, null=True)
def get_absolute_url(self):
return reverse('orders')
class Customer(models.Model):
status = models.CharField(max_length=10, null = True, choices=STATUS_CHOICES)
name = models.CharField(max_length=200, null=True)
username = models.CharField(max_length=25, null = True)
email = models.EmailField(null=True)
def __str__(self):
return str(self.name)+" - " +str(self.username)
def get_absolute_url(self):
return reverse('customers')
The View:
class OrderCreateView(CreateView):
model = Order
fields = '__all__'
def form_valid(self, form):
return super().form_valid(form)

Save the Logged in User to the database model on form submission with django 1.8

I have a model and I am trying to save the user to the models database when the user submits the form. I had a site that did this but now my editor says "Use of super on an old style class"
I am using django 1.8 and i get
IntegrityError at /auction/createview/ NOT NULL constraint failed:
auction_auction.user_id
which is the nicest error I have been able to get. with all the tinkering i have done
class AuctionCreateView(LoginRequiredMixin,CreateView):
model = Auction
action = "created"
form_class = AuctionForm
auction_form = AuctionForm(initial={'user':request.user})
class AuctionForm(forms.ModelForm):
class Meta:
model = Auction
fields = (
"user",
"item_name",
"reserve",
"start_date",
"end_date",
"description",
"tags",
)
class Auction(models.Model):
user = models.ForeignKey(User)
item_id = models.CharField(max_length=255, blank=True, null=True)
item_name = models.CharField(max_length=255, blank=True, null=True)
winner = models.ForeignKey(User, related_name='Auction_Winner', blank=True, null=True)
reserve = MoneyField(max_digits=10, decimal_places=2, default_currency='USD')
created = models.DateTimeField(editable=False, null=True)
slug = AutoSlugField(('slug'), max_length=128, unique=True, populate_from=('item_name',))
start_date = models.DateTimeField(verbose_name="Start date")
end_date = models.DateTimeField(verbose_name="End date")
active = models.BooleanField(default=False, verbose_name='Active')
total_bids = models.IntegerField(default=0, verbose_name='Total bids')
date_added = models.DateTimeField(auto_now_add=True, verbose_name='Date added')
last_modified = models.DateTimeField(auto_now=True, verbose_name='Last modified')
description = models.TextField(max_length=3000)
tags = tagging.fields.TagField()
# bid_set = models.IntegerField(default= 0, verbose_name = "Bid set")
starting_amount = MoneyField(max_digits=10, decimal_places=2, default_currency='USD')
def __unicode__(self):
return '%s selling %s' % (self.user, self.item_name)
def _get_increment(self):
""" add some logic to base incrementing amount on starting price """
def get_absolute_url(self):
return reverse('auction_detail',
kwargs={'slug': self.slug})
when i saw this post I thought i'd be able to figure it out. thanks  ★ ✩
You need insert user_id before form save.
AuctionForm - need update request.user value. Added this fields from form initial.
You have to include 'user' on the fields of the Auction form class to solve that error and just put an initial parameter on the form instance in the views.py like
auction_form = AuctionForm(initial={'user':request.user})
because request.user on the form_valid method will not work at all

How to create an inline formset for a reverse foreign key relationship

I have a Property Model as follows =
class Property(models.Model):
property_type = models.CharField(max_length=255, default='Apartment')
specifications = models.CharField(max_length=255, default='Basic')
built_up_area = models.FloatField(max_length=6, null=False, default=0)
total_area = models.FloatField(null=False, default=0)
number_of_bedrooms = models.CharField(max_length=3, default=1)
number_of_bathrooms = models.CharField(max_length=3, default=1)
number_of_parking_spaces = models.CharField(max_length=2, default=0)
address_line_one = models.CharField(max_length=255, null=False)
address_line_two = models.CharField(max_length=255, null=True, default=None)
connectivity = models.CharField(max_length=255, default=None, null=True)
neighborhood_quality = models.CharField(max_length=255, default=None,
null=True)
comments = models.CharField(max_length=255, default=None, null=True)
city = models.ForeignKey('City')
state = models.ForeignKey('State')
pin_code = models.ForeignKey('PinCode')
developer = models.ForeignKey('Developer', null=True, default=None)
owner = models.ForeignKey('Owner', null=True, default=None)
created_by = models.ForeignKey('custom_user.User')
project = models.ForeignKey('Project')
def __unicode__(self):
return self.property_type
class Meta:
verbose_name_plural = 'Properties'
And a City model as follows -
class City(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(City, self).save(*args, **kwargs)
def __unicode__(self):
return self.name
Now I want to make a single form where I can enter the Property details and while entering the city, I can enter the name of the city instead of selecting from the dropdown list.
So how do I create the inline formset using the inlineformset_factory to create the form?
==EDIT==
I've tried to use the following code to create the formset
CityFormset = inlineformset_factory(City, Property,
fields=('city',),
extra=0,
min_num=1,
can_delete=False)
You've misunderstood what an inline formset is. It's for editing the "many" side of a one-to-many relationship: that is, given a parent model of City, you could edit inline the various Properties that belong to that city.
You don't want a formset at all to simply edit the single City that a property can belong to. Instead, override the city field within your Property form to be a TextField, and either create a new City or find an existing one in the clean_city method.
class PropertyForm(forms.ModelForm):
city = forms.TextField(required=True)
class Meta:
model = Property
exclude = ('city',)
def __init__(self, *args, **kwargs):
super(PropertyForm, self).__init__(*args, **kwargs)
if self.instance and not self.data:
self.initial['city'] = self.instance.city.name
def save(self, commit=True):
city_name = self.cleaned_data['city']
city, _ = City.objects.get_or_create(name=city_name)
instance = self.save(commit=False)
instance.city = city
if commit = True:
instance.save()
return instance