Stuck on linking ManytoMany relationships with Modelform - python

I'm pretty new to Django and I am working on a project that currently requires the following:
I have two basic structures: a Project model and a TeamMember model- both related to each other through a ManytoMany relationship. Then I have an TMAssigned 'through' class. The team member will have many projects assigned to it over time.
I have a ModelFrom which creates a Project model through the creation of the form.
My question is, How do I link the team member to the newly created project upon the submission of the form?
Here is a bit of my model & form code:
TeamMember
class TeamMember(models.Model):
firstname = models.CharField(max_length=100, default= "First Name")
lastname = models.CharField(max_length=100, default= "Last Name")
fullname = models.CharField(max_length=100, default= "Full Name")
email = models.EmailField(max_length=254)
cellphone = PhoneNumberField(null=False, blank=False, unique=True)
numberofcases = models.IntegerField(max_length=10000, default=0)
#property
def fullnamefunc(self):
fullname = "{} {}".format(self.firstname, self.lastname)
return fullname
def __str__(self):
return self.fullname
Project
class Project(models.Model):
pursuitname = models.CharField(max_length=500)
datecreated = models.DateTimeField(auto_now=True)
bdmember = models.ManyToManyField('team.TeamMember')
Views.py
class bdFormView(TemplateView):
template_name = os.path.join(BASE_DIR, "templates/masterform/bdform.html")
def get(self,request):
form = bdForm()
return render (request, self.template_name, {'form': form})
def post(self, request):
form = bdForm(request.POST)
if form.is_valid():
print("form is valid")
project = form.save(commit=False)
project.save()
text = form.cleaned_data['briefcard']
Form.py
class bdForm(forms.ModelForm):
bdmemberlist = TeamMember.objects.all().order_by('lastname')
pursuitname = forms.CharField()
bdmember = forms.ModelChoiceField(queryset= bdmemberlist)
addbdteam = forms.ModelMultipleChoiceField(
queryset=TeamMember.objects.all().order_by('lastname'), widget=Select2MultipleWidget, required=False)
class Meta:
model = Project
fields = ['pursuitname','addbdteam','bdmember',]
def __init__(self, *args, **kwargs):
if kwargs.get('instance'):
initial = kwargs.setdefault('initial', {})
initial['projects'] = [t.pk for t in
kwargs['instance'].project_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self, False)
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
for project in self.cleaned_data['bdmember']:
instance.teammember_set.add(project)
Thanks in advance!!
Edit- after doing some more research, I've removed the "Through" model from the script and am trying to rely on the form.py save method to do the join. However, when I do this- the two are still not linking up properly.

Since only your admin (superusers?) will log in, you can start off by using the in-built Django Admin.
I would recommend this for you, at least for now, because you're a beginner and the Admin Form is stunningly simple to use. Then, you can create a custom form later on when you're more comfortable. :-)
With this in mind, you can try eliminating the 'through' table (you may need to reset your migrations), and try this.
Admin.py
from django.contrib import admin
from .models import TeamMember, TMAssigned, Project,
TeamMembersInLine(admin.TabularInline):
model = TeamMember
extra = 1
#admin.register(Project):
class ProjectAdmin(admin.ModelAdmin):
list_display = ('pursuitname', 'bdmember ', 'datecreated')
inlines = [TeamMembersInLine]
Here's another answer that delves into the through table. It was asked by someone in your situation and the answer is relevant too.

Related

Django: How to save predefined values in modelForm

Good evening,
is it possible to change the ModelForm inside my forms.py, so that already known values are saved inside the database? For example:
models.py:
class Customer(models.Model):
name = models.CharField(max_length=255)
class Project(models.Model):
customer = models.ForeignKey(Customer, null=False, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
class Entry(models.Model):
user = ...request.user.id?...
customer = models.ForeignKey(Customer, null=False, on_delete=models.CASCADE)
project= models.ForeignKey(Project, null=False, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
forms.py:
class EntryForm(ModelForm):
class Meta:
model = Entry
fields = '__all__'
def __init__(self, *args, pk, **kwargs):
super().__init__(*args, **kwargs)
self.fields['project'].queryset = Project.objects.filter(customer_id=pk)
When entering the knew site, I already know about the only possible Customer (pk)! I don't want to place a choicefield inside my knew site, but the customer should be saved inside my database nonetheless! Same goes for the active user (request.user), respectively the id (request.user.id). Can this data be passed into the modelForm as well?
Did someone else also had this problem and might know the solution? What do I have to change inside my modelForm to make it work?
Thanks for all your efforts and a happy weekend to all of you!
You don't have to. You can simply exclude the customer field from the fields:
class EntryForm(ModelForm):
class Meta:
model = Entry
exclude = ['customer']
def __init__(self, *args, pk, **kwargs):
super().__init__(*args, **kwargs)
self.fields['project'].queryset = Project.objects.filter(customer_id=pk)
Then in the view where you use the EntryForm, you can thus implement this as:
def my_view(request, customer_id):
if request.method == 'POST':
form = EntryForm(request.POST, request.FILES, pk=customer_id)
if form.is_valid():
form.instance.customer_id = customer_id
form.save()
return redirect('name-of-some-view')
else:
form = EntryForm(pk=customer_id)
return render(request, 'name_of_template.html', {'form': form})
You thus can "inject" data, by setting it at form.instance.

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.

How to change a field in parent class while creating chiled class fields in forms.py and views.py?

I'm was creating ModelForm I try to make change the parent class while saving child class fields to the database, in the views.py I made but it didn't save to the database.
here is my model.py
class Table(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
book = models.BooleanField(default=False)
class People(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
taple = models.OneToOneField(Table, on_delete=models.CASCADE, null=True, blank=True)
#receiver(post_save, sender=User)
def update_people_profile(sender, instance, created, **kwargs):
try:
instance.people.save()
except ObjectDoesNotExist:
People.objects.create(user=instance)
Class People is the child class and Table is the parent class so I'm using People class for making forms. here is my forms.py
class Booking(forms.ModelForm):
class Meta:
model = People
fields = [
'taple',
]
So I want to make True book field in Table class and save it to the database when saving Booking form. here is my views.py
def booking(request):
if request.method == 'POST':
try:
people_instance = People.objects.get(user=request.user)
except Table.DoesNotExist:
people_instance = People(user=request.user)
form = Booking(request.POST, instance=people_instance)
if form.is_valid():
user = form.save(commit=False)
user.taple.booking = True
user.refresh_from_db()
user.user = request.user
user.taple = form.cleaned_data.get('taple')
user.save()
print(user.taple.booking, user.taple.id)
return redirect('booked')
else:
form = Booking()
return render(request, 'main/booking.html', {'form': form})
Any Idea?
What I understand from the snippets is that you want to be able to record if a table is booked (book Boolean Field in your Table model and if so by whom, which is the object of your People model.
If my understanding is correct, then I don't think you really need a join table (People model). Instead, I would change your model as follow:
class Table(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
booked_by = models.OneToOneField(User, null=True, on_delete=models.SET_NULL, related_name='table_booked')
#property
def is_booked(self):
# This returns True if booked_by is set, False otherwise
return self.booked_by_id is not None
This way you don't need the People model. The property decorator will allow you to use is_booked as a calculated field.
Also, note the related name which will be used in the form:
class BookingForm(forms.ModelForm):
table_booked = forms.ModelChoiceField(queryset=Table.objects.filter(booked_by__isnull=True))
class Meta:
model = User
fields = ['table_booked',]
In the form, you will see that we define a custom queryset for table_booked. THe aim is to filter for free tables only.
Then you can hopefully simplify as well your view as follow:
Update:
As table_booked is a reverse foreign key, we actually need to save the table object which contains the relation. Here is the modified view:
#login_required
def booking(request):
form = BookingForm(request.POST or None, instance=request.user)
if form.is_valid():
user = form.save(commit=False)
tbl = form.cleaned_data['table_booked']
tbl.booked_by = request.user
tbl.save()
user.save()
print(request.user.table_booked.id, request.user.table_booked.is_booked)
return redirect('/')
return render(request, 'booking/booking.html', {'form': form})
Note: I haven't tested the code so there could be some typos but that should help you getting started.

Django Inline form validation ignored partly filled form

I have got the code from https://github.com/adandan01/mybook, the code is working fine, even when I have updated it to Django 2. It's very simple project for adding a person in a form, and his/her relatives in the inline form. Everything works but when I add a relative name and forget to add his relationship, and submitted the form, unfortunately, that record will not pass the validation but will give no error messages as well. Django will ignore the entire record. For example, the record for Hawra in the image, will not be saved and Django will remove it. For this simple App there are only two fields to be filled (name and relationship), but I'm working on app with 8 fields, and it will be difficult to lose the data. is there any way to make django do the validation in the formset/subform as long as any fields have data and will ask the user to fill all required fields?
models.py:
class Profile(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
created_date = models.DateTimeField(default=timezone.now)
def get_absolute_url(self):
return reverse('profile-update', kwargs={'pk': self.pk})
def __unicode__(self):
return "%s %s" % (self.first_name, self.last_name)
class FamilyMember(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.PROTECT)
name = models.CharField(max_length=100)
relationship = models.CharField(max_length=100)
form.py
class ProfileForm(ModelForm):
class Meta:
model = Profile
exclude = ()
class FamilyMemberForm(ModelForm):
class Meta:
model = FamilyMember
exclude = ()
FamilyMemberFormSet = inlineformset_factory(Profile, FamilyMember,
form=FamilyMemberForm, extra=1)
views.py
class ProfileCreate(CreateView):
model = Profile
fields = ['first_name', 'last_name']
class ProfileFamilyMemberCreate(CreateView):
model = Profile
fields = ['first_name', 'last_name']
success_url = reverse_lazy('profile-list')
def get_context_data(self, **kwargs):
data = super(ProfileFamilyMemberCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['familymembers'] = FamilyMemberFormSet(self.request.POST)
else:
data['familymembers'] = FamilyMemberFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
familymembers = context['familymembers']
with transaction.atomic():
self.object = form.save()
if familymembers.is_valid():
familymembers.instance = self.object
familymembers.save()
return super(ProfileFamilyMemberCreate, self).form_valid(form)
I found the solution here django inline_formset - form.empty_permitted = False doesn't work
I had to add the following code before if (familymembers.is_valid():...) in the create and update class, so, now Django will show the error if I entered data in the Name field only and will tell me the Relationship field is required.
if familymembers.is_valid() == False:
return self.render_to_response(self.get_context_data(form=form,familymembers=familymembers ))

How can I render a ManyToManyField as checkboxes?

I'm making a game link site, where users can post links to their
favorite web game.
When people post games they are supposed to check what category the
game falls into.
I decided to allow many categories for each game since some games can
fall into many categories.
So the question is, how do I handle this in my view?
And how can I show it as Checkboxes, where at least one has to be
checked?
And how can I show this as checkboxes in the Admin as well?
Here is the code
Models:
class Category(models.Model):
category = models.CharField(max_length=200)
def __unicode__(self):
return self.category
class Game(models.Model):
name = models.CharField(max_length=200)
url = models.CharField(max_length=200)
poster = models.ForeignKey(User, related_name='game_poster_set')
postdate = models.DateTimeField(default=datetime.now)
cats = models.ManyToManyField(Category)
hits = models.IntegerField(default=0)
post = models.BooleanField(default=False)
Views:
def submit(request):
form = GameForm(request.POST or None)
if form.is_valid():
game = form.save(commit=False)
game.poster = request.user
game.save()
next = reverse('gamesite.games.views.favorites')
return HttpResponseRedirect(next)
return render_to_response(
'games/submit.html',
{'form': form},
context_instance = RequestContext(request),)
Forms:
class GameForm(forms.ModelForm):
name = forms.CharField(max_length=15, label='Name')
url = forms.URLField(label='URL', initial='http://')
class Meta:
model = Game
fields = ('name','url')
Thanks!
class GameForm(forms.ModelForm):
name = forms.CharField(max_length=15, label='Name')
url = forms.URLField(label='URL', initial='http://')
cats = forms.ModelMultipleChoiceField(
queryset=Category.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=True)
class Meta:
model = Game
fields = ('name','url','cats')
that should fix your view, but i'm not sure about the admin. still looking... will edit if i find anything.
Here is how I solved it (Edit: and the admin thing)
Forms:
cats = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=Category.objects.all())
(It was the queryset part I couldn't find..)
View:
cats = form.cleaned_data['cats']
game.cats = cats
And that's all the code needed to save the data.
Edit:
here is a solution for the admin
Models:
from django.contrib import admin
from django.forms import CheckboxSelectMultiple
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.ManyToManyField: {'widget': CheckboxSelectMultiple},
}
Admin:
from gamesite.games.models import Game, MyModelAdmin
admin.site.register(Game, MyModelAdmin)
It's kind of quirky in looks, but works!
If someone finds a way to make it more "clean" please post!
Cheers!
Found this on from Chase Seibert, Engineering Manager of Dropbox
Source from Chase Seibert
from django.db import models
from django.forms.models import ModelForm
from django.forms.widgets import CheckboxSelectMultiple
class Company(models.Model):
industries = models.ManyToManyField(Industry, blank=True, null=True)
class CompanyForm(ModelForm):
class Meta:
model = Company
fields = ("industries")
def __init__(self, *args, **kwargs):
super(CompanyForm, self).__init__(*args, **kwargs)
self.fields["industries"].widget = CheckboxSelectMultiple()
self.fields["industries"].queryset = Industry.objects.all()

Categories

Resources