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