How to handle formset for multiple instances? - python

I'm building a match system for a Tournament Manager. I have a "Match" model and "Set" model (code down below). First, I'd like to have a form that regroups all sets related to one match, how can I do that ? Secondly, how can I handle this if I have several matchs in my template ?
models.py
class Match(models.Model):
isFinished = models.BooleanField(default=False)
team1Win = models.BooleanField(default=False)
team2Win = models.BooleanField(default=False)
phase = models.ForeignKey(Phase, default=None, on_delete=models.CASCADE)
teams = models.ManyToManyField(Team, default=None, blank=True)
class Set(models.Model):
timeSet = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)
scoreTeam1 = models.IntegerField(null=True)
scoreTeam2 = models.IntegerField(null=True)
match = models.ForeignKey(Match, default=None, on_delete=models.CASCADE)
models.py
class SetUpdateForm(forms.ModelForm):
class Meta:
model = Set
fields = [
'scoreTeam1',
'scoreTeam2',
'match',
]
EDIT:
I created my formset, etc... All works perfectly good but I want to make some validation before submitting the formset, how can I do that ?

What you can do is to create an inlineformset that will map the Match and all related Set.
First you need the Match and all related Set:
#I assume you have the match pk from the url
def match_formset_view(request,pk):
match = get_object_or_404(Match, pk = pk)
#get all the related Set
sets = match.set_set.all()
#create the inline formset
MatchSetFormset = forms.inlineformset_factory(
Match,
Set,
form=SetUpdateForm,
min_num=1,
extra=0,
can_delete=True
)
#populate the formset accordingly
formset = MatchSetFormset(request.POST or None,instance=match, queryset= sets,prefix='sets')
#validate the formset
if formset.is_valid():
#do something then save
formset.save()
else:
#do other things.
Be aware that you can't save the formset if you didn't save the Match instance first(for creation).

Related

hidden field changes value when saving form django

This is my model
class Person(models.Model):
name = models.CharField(max_length=100, blank=True, default='')
is_ignore_validations = models.BooleanField(default=False)
and this is my form
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ['name',
'is_ignore_validations',
]
is_ignore_validations = forms.BooleanField(widget=forms.HiddenInput(), required=False)
The usage for is_ignore_validations is in the clean_name() function
def clean_name():
original_name = self.data.get('name')
if not self.initial.get('is_ignore_validations'):
pattern = re.compile('^[a-zA-Z]+$')
if not pattern.match(original_name):
raise ValidationError('name must consist only of letters')
return original_name
I initiate my form with request.POST, which doesn't have is_ignore_validations.
I want it to stay how I've set it manually in the DB, but when I use form.save() it always changes to False.
So firstly, how do I keep is_ignore_validations data in DB always the same?
Secondly, is the usage in clean_name of the variable from initial best practice - self.initial.get('is_ignore_validations')?

Formsets + Many to Many Relationship Problem

I have partially implemented the formsets + many to many relationship feature with an Invoice and Inventory Model.
My problem is the form does not show the available Inventory (even though they exist).
See problem here:
https://www.dropbox.com/s/mtqkfee2pisyh5a/dj005_formset_many_to_many_relationship_working.jpg?dl=0
Here is the working code:
# MODELS.PY
class Invoice_Test_M2M(models.Model):
id = models.BigAutoField(primary_key=True)
ref_num = models.CharField(max_length=100)
def __str__(self):
return self.ref_num
class Inventory_Test_M2M(models.Model):
id = models.BigAutoField(primary_key=True)
inventory_name = models.CharField(blank=True, max_length=100)
invoice = models.ManyToManyField('Invoice_Test_M2M', through= "Invoice_Inventory_Through")
def __str__(self):
return self.inventory_name
class Invoice_Inventory_Through(models.Model):
invoice = models.ForeignKey(Invoice_Test_M2M, on_delete=models.CASCADE)
inventory = models.ForeignKey(Inventory_Test_M2M, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True)
quantity = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True)
amount = models.DecimalField(max_digits=9, decimal_places=2, blank=True, null=True)
# FORMS.PY
Inventory_TestLineItem_M2M_Formset = inlineformset_factory(Invoice_Test_M2M, Invoice_Inventory_Through, fields = '__all__', exclude=[], can_delete=True)
# VIEWS.PY
class Invoice_M2M_CreateView(CreateView):
model = Invoice_Test_M2M
fields = '__all__'
def get_context_data(self, **kwargs):
context = super(Invoice_M2M_CreateView, self).get_context_data(**kwargs)
if self.request.POST:
context['track_formset'] = Inventory_TestLineItem_M2M_Formset(self.request.POST)
else:
context['track_formset'] = Inventory_TestLineItem_M2M_Formset()
return context
def form_valid(self, form):
context = self.get_context_data(form=form)
formset = context['track_formset']
if formset.is_valid():
response = super().form_valid(form)
formset.instance = self.object
formset.save()
return response
else:
return super().form_invalid(form)
Is there a way around this?
PS. INTERESTINGLY, if I just use a pseudo many to many MODEL (i.e. using a foreign field and not a many to many field). It works.
You can see it working here:
https://www.dropbox.com/s/32x84k8roa88jvf/dj005_formset_many_to_many_relationship_working_B.jpg?dl=0
So why not use this method? The main reason is the M2M has a built-in API in showing its relevant members (i.e. Shows the inventories of a specific Invoice). In the method above, I am under the impression I have to do it manually.
P.P.S. Related Stackoverflow post. I already implemented the solutions but I still get the same problem:
pendant to inline formsets for many-to-many relations
Accessing Many to Many "through" relation fields in Formsets
And just like that. After posting the question, I found the answer.
Stupid me.
Basically, the code I posted above works.
The problem is I created a new model so technically speaking its empty so I couldn't find them in my drop down list.

How to update form with get approval from another specific user in Django?

I want to create a basic approval system in my Django project. In this system there are several ranks, but for this question I only use Lead and Manager. I created forms and this forms are representing limits.
Only Lead can fill these forms. But what I want is when a Lead update the form it shouldn't display without Manager's approval. How can I do that?
approvals/models.py
class DoaTable(models.Model):
LIMITS = (
('Low Risk', 'Low Risk'),
(...),
('Strict Credit Check', 'Strict Credit Check'),
('No Credit Check', 'No Credit Check'),
)
RANKS = (
('Analyst', 'Analyst'),
('Senior Analyst', 'Senior Analyst'),
('Lead', 'Lead'),
('Manager', 'Manager'),
('...Officer'),
)
rank = models.CharField(max_length=200, choices=RANKS)
risk = models.CharField(max_length=200, choices=LIMITS)
limit = models.FloatField()
comp_name = models.ForeignKey(CompanyProfile, on_delete=models.CASCADE, null=True)
user/models.py
class UserProfile(AbstractUser):
...
password = models.CharField(max_length=250)
email = models.EmailField(max_length=254)
rank = models.CharField(max_length=200)
...
class Rank(models.Model):
rank_name = models.CharField(max_length=200)
company = models.ForeignKey(CompanyProfile, on_delete=models.CASCADE, null=True, unique=False)
Ranks in this model is same as Doa table ranks. We assume that user ranks are Lead and Manager for this scenerio.
approvals/forms.py
class DoaTableForm(forms.ModelForm):
class Meta:
model = DoaTable
fields = ('rank', 'risk', 'limit',)
class UpdateDoaTableForm(forms.ModelForm):
class Meta:
model = DoaTable
fields = ('limit',)
aprovals/views.py
def update_limit(request, id):
limiting = get_object_or_404(DoaTable, id=id)
form = UpdateDoaTableForm(request.POST or None, request.FILES or None, instance=limiting)
limiting_item = DoaTable.objects.filter(id=id)
if form.is_valid():
form.save()
return redirect('approvals:update_limit_list')
context = {
'form': form,
'limiting_item': limiting_item
}
return render(request, 'limitUpdate.html', context)
1. How to do it with your current architecture
Add a new column to your DoaTable model to reflect whether it should be displayed or not and only display it in your view if doatable.should_display is True:
approvals/models.py
class DoaTable(models.Model):
# ....
should_display = models.BooleanField(default=False)
rank = models.CharField(max_length=200, choices=RANKS)
# ...
Then override your ModelForm's __init__() to accept the current user and clean() method to check for the rank:
approvals/forms.py
from django.core.exceptions import ValidationError
# ...
class UpdateDoaTableForm(forms.ModelForm):
class Meta:
model = DoaTable
fields = ('limit',)
def __init__(self, *args, user, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
def clean(self):
cleaned_data = super().clean()
if self.user.rank != "Lead": # BAD: hardcoded value
raise ValidationError(
"You do not have the required rank."
)
return cleaned_data # Always return the cleaned data
Pass in the request.user in your view:
approvals/views.py
def update_limit(request, id):
# ...
form = UpdateDoaTableForm(request.POST or None, request.FILES or None, user=request.user, instance=limiting)
# ...
2. Suggested ways of doing it
AbstractUser comes in with groups and permissions which you can utilize to check if your user belongs to a certain group or has a certain permission before doing an action (in this case updating/approving forms), for example permissions could be: 'fill_form_perm', 'approve_form_perm' and your groups could be: 'lead', 'officer'.
You can make use of IntegerChoices for the ranks in your model then check the level of permission your user has by a doing a simple comparison. This is more flexible as you can chain in multiple ranks, for example below Manager but above Senior Anaylist in one condition without too much of a hassle.

Django 3 - Making Model's FK Dropdown Display Current User's Data Only

I'm creating my first app with Django and still have a lot to learn, but right now I am completely stuck and need some help. I have a model for Customers and Tickets. I have it so different users can save new customers/tickets and only view their data from the dashboard once logged in. However, when creating a new ticket, there is a dropdown option to select customer for the ticket - and the current user is able to see every users customers.
Here is the code, I'll share more code if needed, but I think this covers what I have going on...
forms.py
class TicketForm(ModelForm):
class Meta:
model = Ticket
fields = ['number', 'customer','date_created','work_description','mechanics','status']
views.py
def createTickets(request):
form = TicketForm()
if request.method == 'POST':
form = TicketForm(request.POST)
if form.is_valid():
newticket = form.save(commit=False)
newticket.shopowner = request.user
newticket.save()
return redirect('tickets')
context = {
'form': form
}
return render(request, 'createticket.html', context)
models.py
class Ticket(models.Model):
def default_number():
no = Ticket.objects.count()
return no + 1
shopowner = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
number = models.CharField(max_length=30, unique=True, default= default_number)
customer = models.ForeignKey(Customer, default=1, on_delete= models.SET_DEFAULT, blank=True)
date_created = models.DateField(default=timezone.now)
work_description = models.TextField(verbose_name="Service Details: ")
mechanics = models.ForeignKey(Mechanic, default=1, on_delete=models.DO_NOTHING, blank=True, verbose_name="Mechanic")
status = models.BooleanField(default=True, verbose_name="Open Ticket")
class Meta:
verbose_name_plural = "Tickets"
I need the Customer foreignkey to only display customers of the current user (or 'shopowner') - same thing for mechanic and eventually vehicle but I can figure those out once I know how to get the customer input to display the correct data.
You'll need to customize your form a bit, in order to modify the queryset for that particular field. We also need to pass a user from the view:
forms.py
class TicketForm(ModelForm):
class Meta:
model = Ticket
fields = ['number', 'customer', 'date_created', 'work_description', 'mechanics', 'status']
def __init__(self, user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if user:
self.fields['customer'].queryset = Customer.objects.filter(shopowner=user)
views.py
def createTickets(request):
form = TicketForm(user=request.user)
# ...
Exactly how you define the queryset is going to depend on how you've defined the relationship between Customer and Shopowner, but this should give you the right approach.

Model Choice Field - get the id

I am busy trying to get the id only in integer format preferably for the ModelChoiceField. I get the list to display but get's returned in a string format. Please helping me in retrieving the id of ModelChoiceField. I think I need to do this in the view.
forms.py
class ProjectForm(forms.ModelForm):
items = forms.ModelChoiceField(queryset=Project.objects.all())
class Meta:
model = Project
fields = ['items']
models.py
class Project(models.Model):
items = models.IntegerField(default=0, blank=True, null=True)
views.py
def ProjectView(request):
form = ProjectForm(request.POST)
if request.method == 'POST':
if form.is_valid():
save_it = form.save(commit=False)
save_it.save()
return HttpResponseRedirect('/')
else:
form = ProjectForm()
return render(request, 't.html', {'form': form })
From what I can tell, items should never be an IntegerField. Your usage has it set up to be a ForeignKey to a Project so you should just make that explicit
items = models.ForeignKey('self', null=True, blank=True)
Possibly with a better descriptive name than items.
Then, you don't need to define anything on the form, it just becomes a standard model form, with a standard model form usage.

Categories

Resources