ManyToManyField does not save in django - python

I have a situation again, when I do a form.save(), my form saves only the parent table, it does not save the intermediary table which is required for Many-To-Many relationships.
My models.py look like this
class Platform(models.Model):
id = models.AutoField(primary_key=True)
description = models.TextField(blank=True)
annotation_file_archived_location = models.FileField(upload_to='msrb/platform')
anntation_file_hashsum = models.TextField()
annotation = models.TextField(unique=True)
def __unicode__(self):
return self.annotation
class Meta:
managed = True
db_table = 'platform'
class Dataset(models.Model):
dataset_id = models.TextField(primary_key=True)
title = models.TextField(blank=True, null=True)
taxonomy = models.ForeignKey('Organism', blank=True, null=True)
citation = models.TextField(blank=True, null=True)
summary = models.TextField(blank=True, null=True)
contributor = models.TextField(blank=True, null=True) # This field type is a guess.
submitted = models.DateField(blank=True, null=True)
last_updated = models.DateField(blank=True, null=True)
author = models.ForeignKey('Users', db_column='author', blank=True, null=True)
platforms = models.ManyToManyField(Platform,through='DatasetPlatform')#,through_fields=('Platform:platform','dataset'))
class Meta:
managed = True
db_table = 'dataset'
class DatasetPlatform(models.Model):
id = models.IntegerField(primary_key=True)
platform = models.ForeignKey(Platform, null=False)
dataset = models.ForeignKey(Dataset,null=False)
class Meta:
managed = False
db_table = 'dataset_platform'
Forms.py
class DatasetForm(forms.ModelForm):
dataset_id = forms.CharField(required=True,help_text="dataset_id")
title = forms.CharField(required=True,help_text="title")
taxonomy = forms.ModelChoiceField(queryset=Organism.objects.all(),empty_label=None,help_text='Taxonomy')
citation = forms.CharField(required=True,help_text="citation")
summary = forms.CharField(required=True,help_text="summary")
contributor = forms.CharField(help_text="contributor (separated by comma)")
submitted = forms.DateField(initial = datetime.now,required=True,help_text="Submitted date")
last_updated = forms.DateField(initial = datetime.now,required=True,help_text="Last Updated date")
platform = forms.ModelMultipleChoiceField(queryset=Platform.objects.all(),help_text="Choose the platforms this dataset belongs to")
class Meta:
model = Dataset
fields = ('dataset_id','title','taxonomy','citation','summary','contributor','submitted','last_updated','platform')# Add author later ,'author')
views.py
def add_dataset(request):
context_dict = {}
if request.method == 'POST':
form = DatasetForm(request.POST)
if form.is_valid():
print "------------------------------------------------------------------------------"
print form.cleaned_data['platform']
form.save()
print "------------------------------------------------------------------------------"
return HttpResponseRedirect('/msrb/')
else:
print form
print form.errors
else:
form = DatasetForm()
context_dict['form'] = form
template = get_template('msrb/add_dataset.html')
context = RequestContext(request,context_dict)
return HttpResponse(template.render(context))
I have tried saving the data using
form.save(commit=True)
form.save_m2m()
form.cleaned_data gives the proper output.
I am not sure what am I missing here as I dont get an error message from django too.
EDIT
I have a workaround for the problem, but I am not sure if this is the best solution. If I can get a better solution, I will be greatful.
def add_dataset(request):
context_dict = {}
if request.method == 'POST':
form = DatasetForm(request.POST)
if form.is_valid():
print form.cleaned_data['platform']
f = form.save()
for p in form.cleaned_data['platform']: <--- Added
d = DatasetPlatform(dataset = f,platform = p) <--- Added
d.save() <--- Added
return HttpResponseRedirect('/msrb/')
else:
print form
print form.errors
else:
form = DatasetForm()
context_dict['form'] = form
template = get_template('msrb/add_dataset.html')
context = RequestContext(request,context_dict)
return HttpResponse(template.render(context))

Django is not able (well, refuses) to automatically save m2m relations with a custom through model. Saving the form data uses direct assignment to the ManyToManyField, which will not work as explained here.
If removing the custom through model is an option, I'd do that. Granted, it will have to be managed = True, but it greatly simplifies use of the field. You're not saving any extra data in the relationship, so it might be an option.
Otherwise, you have already found the only workaround. Each time you want to manipulate the m2m relationship, you'll have to manually create, alter and delete the DatasetPlatform instances. Again, this is explained in further detail in the relevant documentation.

Related

django: foreign key issues when creating a model object

I am trying to write a row to database, with data gathered in a form. I need to work with two foreign keys and one of them is causing the creating to fail, although I am unable to figure out why:
here is my model:
def upload_path(instance,file):
file_dir = Path(file).stem
print('usr',instance.user.id)
path = '{}/{}/{}/{}'.format(instance.user.id,"projects",file_dir,file)
return path
class BuildingFilesVersions(models.Model):
version_id = models.AutoField(primary_key=True)
building_id = models.ForeignKey(Building, on_delete=models.CASCADE,related_name='building_id_file')
user = models.ForeignKey(Building, on_delete=models.CASCADE,related_name="user_file")
created_at = models.DateTimeField(auto_now_add=True, blank=True)
description = models.TextField(max_length=200, blank=True, null=True)
modification_type = models.CharField(choices=WORK_TYPE_CHOICES, max_length=200, blank=True, null=True)
filename = models.CharField(max_length=200, blank=True, null=True)
file = models.FileField(upload_to=upload_path, null=True, blank=True)
and here is my view:
#login_required
#owner_required
def RegisterFileView(request,pk):
form = AddBuildingFileForm()
if request.method == 'POST':
form = AddBuildingFileForm(request.POST,request.FILES)
if form.is_valid():
description = form.cleaned_data["description"]
modification_type = form.cleaned_data["modification_type"]
filename = form.cleaned_data["modification_type"]
file = request.FILES['file'].name
BuildingFilesVersions.objects.create(building_id_id=pk,
user_id=request.user,
description=description,
modification_type=modification_type,
filename=filename,
file=file)
return redirect('home')
else:
form = AddBuildingFileForm()
context = {'form':form}
return render(request, 'building_registration/register_file.html', context)
what gets me confused is that the error is Field 'building_id' expected a number but got <SimpleLazyObject: <User: Vladimir>> even though pk return the proper building_id
Can anyone see where I messed up?
to access the id of the foreign key add a double underscore
BuildingFilesVersions.objects.create(building_id=Building.objects.get(pk=pk),
user=request.user,
description=description,
modification_type=modification_type,
filename=filename,
file=file)
Your user must be logged in to assign him in the Model
Answer for Similar Question
See the Docs

problem with exporting items from current formset(s) in Django 3.2

I have a view where the user inserts a number of units and once submit button is clicked the excel file is exported as xlsx file. I am using Django import-export to do that BUT I don't know how to filter the CERequests model so the user sees only what (s)he has just inserted. I have implemented the filtering by the user but when clicking submits button it filters all items by the current user but it shows all of them (also items from the past).
What I want is to export only values from the current formset or formsets. What I tried is to put created=created in filter method but it gives me an empty Excel file. When I remove it gives me a list of all CERquests that the user inserted.
What do I need to do to get only data from the current formset(s)?
views.py
class CostCalculator(LoginRequiredMixin, TemplateView):
template_name = 'calculator/cost_calculator.html'
def get(self, *args, **kwargs):
# Create an instance of the formset
formset = CalculatorForm(initial=[{
'author': self.request.user.email,
}])
return self.render_to_response({'ce_request_formset': formset})
# Define method to handle POST request
def post(self, *args, **kwargs):
formset = CalculatorForm(data=self.request.POST)
# Check if submitted forms are valid
if formset.is_valid():
for form in formset:
related_product = form.cleaned_data.get('related_product')
created = form.cleaned_data.get('created')
form.save()
qs = CERequest.objects.filter(related_product__title=related_product, created=created)
dataset = CERequestResource().export(qs)
response = HttpResponse(dataset.xlsx, content_type="xlsx")
response['Content-Disposition'] = 'attachment; filename=filename.xlsx'
return response
return self.render_to_response({'ce_request_formset': formset})
forms.py
class CalculatorForm(forms.ModelForm):
author = forms.CharField(required = False)
number_of_units = forms.IntegerField(help_text='Only numeric values are allowed.', min_value=0)
total_price = forms.IntegerField(widget = forms.HiddenInput(), required = False)
created = forms.DateTimeField(widget = forms.HiddenInput(), required = False)
class Meta:
model = CERequest
fields = ('author', 'related_product', 'related_component', 'number_of_units', 'total_price')
readonly_fields = ('created')
CalculatorForm = formset_factory(CalculatorForm)
models.py
class CERequest(models.Model):
author = models.CharField(max_length=255, blank=True, null=True)
related_component = models.ForeignKey(CostCalculator, on_delete=models.CASCADE, blank=True, null=True)
number_of_units = models.IntegerField(default=0)
related_product = models.ForeignKey(Product, on_delete=models.CASCADE, null=True, blank=True)
created = models.DateTimeField(auto_now=True)
total_price = models.IntegerField(default=0, blank=True, null=True)

How to use default field values for ForeignKey?

I'm just starting to learn Django and building a simple blog with it.
So i have two models Post and PostStatistics. When ever i add a new post, i want that PostStatistics contains all specified default values. How can i achieve this correctly?
models.py
class PostStatistics(models.Model):
id = models.UUIDField(primary_key=True, default=uuid4)
post_views = models.IntegerField(default=0)
post_likes = models.IntegerField(default=0)
post_favorites = models.IntegerField(default=0)
class Post(models.Model):
id = models.UUIDField(primary_key=True, default=uuid4)
user = models.ForeignKey(UserProfile, null=True, on_delete=models.CASCADE)
statistics = models.ForeignKey(PostStatistics, null=True, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
body = RichTextField(blank=True, null=True)
draft = models.BooleanField(default=False)
views.py
def add_post(request: HttpRequest):
form = PostForm(request.POST)
is_draft = True if form.data.get("draft") == "on" else False
post = Post(
title=form.data["title"],
body=form.data["post"],
user=request.user,
draft=is_draft,
statistics = PostStatistics() -> this is not correct
)
post.save()
return redirect("post")
At the moment i get FOREIGN KEY constraint failed.
You create a new one:
def add_post(request: HttpRequest):
form = PostForm(request.POST)
is_draft = form.data.get('draft') == 'on'
post_statistics = PostStatistics.objects.create()
Post.objects.create(
title=form.data['title'],
body=form.data['post'],
user=request.user,
draft=is_draft,
statistics = post_statistics
)
return redirect('post')
It however does not make much sense to store the statistics in a separate model, since there is a clear one-to-one relation, and thus the statistics can be stored in the Post model.
Furthermore you can use the form to validate the input and also create the object (or at least parts of it). A better modeling thus might be:
from django.conf import settings
class Post(models.Model):
id = models.UUIDField(primary_key=True, default=uuid4)
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE, editable=False)
title = models.CharField(max_length=200)
body = RichTextField(blank=True, null=True)
draft = models.BooleanField(default=False)
post_views = models.IntegerField(default=0, editable=False)
post_likes = models.IntegerField(default=0, editable=False)
post_favorites = models.IntegerField(default=0, editable=False)
and then work with a ModelForm where you let the form do all the proper validation and cleaning:
from django.contrib.auth.decorators import login_required
#login_required
def add_post(request: HttpRequest):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
form.instance.user = request.user
form.save()
return redirect('post')
else:
form = PostForm()
return render(request, 'name-of-some-template.html', {'form': form})
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].

Django Form initial Value For Foreignkey Field

I have two django models which are :
class Dataset(models.Model):
name = models.CharField(max_length = 200)
description = models.CharField(max_length=1000)
owner = models.ForeignKey(Profile, null=True, on_delete=models.SET_NULL)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Source(models.Model):
name = models.CharField(max_length = 200)
description = models.CharField(max_length=1000)
dataset = models.ForeignKey(Dataset, null=True, on_delete=models.SET_NULL)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
When saving a Source, I would like to initiate the value of the corresponding Dataset. I tried to initiate the value of my form as suggested here : foreign key as initial value not passed to the ModelForm in django
def create_source(request, dataset_id):
user = request.user
dataset = Dataset.objects.get(id=dataset_id)
form = SourceForm(initial={"dataset" : dataset, })
if request.method == "POST":
form = SourceForm(request.POST or None, initial={"dataset" : dataset, })
if form.is_valid():
source = form.save()
# dataset.source_set.add(source) # Only works if I add this line
return redirect("source", dataset_id=dataset_id, source_id=source.id)
context = {"form": form}
return render(request, "sources/source_form.html", context)
The SourceForm:
class SourceForm(ModelForm):
class Meta:
model = Source
fields = "__all__"
exclude = ["dataset"]
The suggested way does not work. I was able to achieve the desired result by adding the commented line above. It is not a recommended solution since it makes a second call to the database.
Any idea how to give properly the dataset object to the source ?
Passing values in initial for excluded fields does not do anything. Instead what you can do is modify the instance wrapped by the form before saving the it:
def create_source(request, dataset_id):
user = request.user
dataset = Dataset.objects.get(id=dataset_id)
form = SourceForm() # No initial
if request.method == "POST":
form = SourceForm(request.POST) # this is a submitted form `request.POST or None` makes no sense use only `request.POST`
if form.is_valid():
form.instance.dataset = dataset
source = form.save()
return redirect("source", dataset_id=dataset_id, source_id=source.id)
context = {"form": form}
return render(request, "sources/source_form.html", context)

Is there a way to take data from a django form and upload it to database?

I am trying to pull data from a Django form and upload it to the database.
Here are my models:
class Category(models.Model):
category = models.CharField(max_length = 128)
def __str__(self):
return f"{self.category}"
class Meta:
verbose_name_plural = 'Categories'
class Listing(models.Model):
user = models.ForeignKey(User, on_delete = models.CASCADE, default = None)
title = models.CharField(max_length = 64)
description = models.TextField(max_length = 128)
bid = models.ForeignKey(Bid, on_delete = models.CASCADE, related_name = 'listing')
url = models.URLField(max_length = 350, blank = True)
category = models.ManyToManyField(Category, related_name='listings')
watchers = models.ManyToManyField(User, blank = True, related_name='listings')
So, when a user adds a new listing, they should choose from categories. Here is how I did it after trying for quite a bit:
def new_listing(request):
if request.method =="POST":
form = ListingForm(request.POST)
if form.is_valid():
bid = Bid(amount = int(request.POST['bid']))
bid.save()
listing = Listing()
listing.bid = bid
listing.user = request.user
listing.title = form.cleaned_data['title']
listing.description = form.cleaned_data['description']
listing.url = form.cleaned_data['url']
listing.save()
listing = Listing.objects.reverse()[0]
for category in form.cleaned_data['category']:
listing.category.add(category)
The issue here is that I have to first save the listing, then pull it again and manually add Categories. I am just wondering if there is a cleaner way of doing this? Or is this how it is done in Django? Cause' I had a really hard time trying to do it cleaner.
Also, the form is Django generated like this:
class ListingForm(ModelForm):
class Meta:
model = Listing
fields = ['title', 'description', 'url', 'category']
Also, also, is this a good practice for creating forms, or should I go with forms.Form?
You are right that you need to save before add when dealing with many to many relationships.
You don't need to do your reverse lookup though, and add accepts many objects, not just one as you can see in the documentation
listing.save()
listing.category.add(*form.cleaned_data["category"])
As for the ModelForm, it's fine to use it since you're trying to get a form to create a model. If it was a custom form to do something else, you'd need to use forms.Form

Categories