This is my first post to SO, so please let me know if I've missed any important details. I am working on updates to a home-grown dJango based ticketing system.
I have two "parent" models (ParentProjects and Projects) that capture details about work we want to track. Both models have a number of columns that store information in the associated tables as well as some FK relations.
The generic class-based detail view is used to view objects in the Project table while the ParentProject table is accessed by a function based view. The function-based view accomplishes the same task of loading parent project object values as the class-based detail view does for the project objects.
The problem I am having is that I cannot add a new entry to the IDTasks model that automatically inserts the Parent Project id. I am able to add a new IDTask from within the admin site (or from the "client" site if I enable the "parent" field within the modelform) by manually selecting the parent I wish to associate the IDTask to. I can also edit and save an existing IDTask from within the Parent Project detail view without any issues. However, when I attempt to add an IDTask using the createview, dJango reports a Not NULL constraint error and the new entry is not saved.
In addition to reviewing and trying many other solutions to this problem, I have disabling the code that automatically adds the logged in user id but am still getting the same null constraint error. What's strange is that I am using the same basic createview structures for adding FK objects to the Projects model and this works perfectly. I'm still getting comfortable with Django's class-based views, so must surely be missing something obvious.
Thank you for your help!
Here are the main views related to my issue:
# Detail view for Parent Projects (function-based)
def ParentProjectDetail(request,parent_id):
parents = ParentProject.objects.get(id=parent_id)
projects = Project.objects.filter(parent_project__pk=parent_id).order_by('project_phase', '-launch_date',)
return render(request, 'otis/parent_detail.html', context={'parents':parents, 'projects':projects })
# Detail View for Projects (class-based)
class ProjectDetailView(generic.DetailView):
model = Project
context_object_name = 'project'
template_name = 'otis/project_detail.html'
# Add parent progress report
class add_idtask_view(SuccessMessageMixin, CreateView):
model = IDTasks
template_name = 'otis/id_report_form.html'
form_class = idTaskForm
success_message = "Report added"
def form_valid(self, form):
idtaskform = form.save(commit=False)
idtaskform.user = self.request.user
self.parents_id = self.kwargs.get('parent_id')
form.instance.ParentProject = get_object_or_404(ParentProject,id=self.parents_id)
return super(add_idtask_view, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('otis:parent_detail', kwargs={'pk': self.parents_id})
Here is the modelform:
class idTaskForm(ModelForm):
class Meta:
model = IDTasks
fields = ('parent_phase','complete','milestones','nextsteps','concerns')
widgets = {
'milestones': Textarea(attrs={'cols': 50, 'rows': 5, 'placeholder': 'Task details...'}),
'nextsteps': Textarea(attrs={'cols': 50, 'rows': 5, 'placeholder': 'Task details...'}),
'concerns': Textarea(attrs={'cols': 50, 'rows': 5, 'placeholder': 'Task details...'}),
}
labels = {
'parent_phase': mark_safe('<span class="required">Phase</span>'),
'complete': mark_safe('<span class="required">Percentage Complete</span>'),
'milestones': ('Milestones'),
'nextsteps': ('Next steps'),
'concerns': ('Concerns'),
}
Here are the two models being accessed:
# Parent Project Model
class ParentProject(models.Model):
class Meta:
verbose_name = "parent project"
verbose_name_plural = "parent projects"
ordering = ['title']
title = models.CharField('Name', max_length=100, null=True, blank=False)
parent_group = models.ForeignKey(ProjectGroups, on_delete=models.CASCADE, blank=True, null=True)
parent_type = models.ForeignKey(ProjectTypes, on_delete=models.CASCADE, null=True, blank=False)
description = models.TextField('description', blank=True)
term_due = models.ForeignKey(Terms, on_delete=models.CASCADE, blank=True, null=True)
term_year_due = models.ForeignKey(Years, on_delete=models.CASCADE, blank=True, null=True)
launch_date = models.DateField('launch date', blank=True, null=True)
parent_phase = models.ForeignKey(SDLCPhases, on_delete=models.CASCADE, null=True, blank=False)
history = HistoricalRecords()
def __str__(self):
return str(self.title)
def get_absolute_url(self):
return reverse('otis:parent_detail', kwargs={'pk': self.pk})
# Reports Model
class IDTasks(models.Model):
class Meta:
verbose_name = "Parent task"
verbose_name_plural = "Parent tasks"
ordering = ['updated_on']
parent = models.ForeignKey(ParentProject, on_delete=models.CASCADE)
parent_phase = models.ForeignKey(SDLCPhases, on_delete=models.CASCADE, null=True, blank=False)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
complete = models.IntegerField('percentage complete', blank=True, null=True, default=0)
milestones = models.TextField('milestones', blank=True)
nextsteps = models.TextField('next steps', blank=True)
concerns = models.TextField('concerns', blank=True)
updated_on = models.DateTimeField(auto_now_add=True, null=True)
history = HistoricalRecords()
def __str__(self):
return str(self.complete)
Here are the url patterns:
# url for the parent project detail view
path('parent_detail/<int:parent_id>/', views.ParentProjectDetail, name='parent_detail'),
# url for the create report accessed within the detail view
path('parent_detail/<int:parent_id>/add_id_report/', views.add_idtask_view.as_view(), name='add_id_report'),
Finally, the template link that invokes the modelform:
link_title
I was able to solve my initial problem by restructuring the view like so:
class add_idtask_view(SuccessMessageMixin, CreateView):
model = IDTasks
template_name = 'otis/id_report_form.html'
form_class = idTaskForm
success_message = "Report added"
def form_valid(self, form):
parents_pid = self.kwargs.get('parent_pid')
self.parent_id = parents_pid
form.instance.user = self.request.user
form.instance.parent_id = parents_pid
return super(add_idtask_view, self).form_valid(form)
def get_success_url(self):
return reverse_lazy('otis:parent_detail', kwargs={'parent_pid': self.parent_id})
The first thing I did was sort out what form fields and database fields I was trying to access. It seems I had confused these and not properly referenced them in my initial view.
Once these were working however, I started getting an error that stated the view was expecting an integer but was getting a string. It seems, for whatever reason, when I used the get_object_or_404 method, the view returns the title of the database object and not the primary key.
Related
I have two Django apps, one with listings and another for accounts. Within the accounts app, I have a reviews model attached to the account, and I want users to have the ability to create a review for a listing that ultimately attaches to the account, not the listing, that way any listing reviews under a particular account are attached to the account itself, not necessarily the listing.
Part of the reviews model is the listing and listing_author fields, that way I can filter on these fields later on. From the listing detail page, I want users to be able to click "Create Review" and create a review for a listing that is under the profile. I am running into an issue where I cannot pass the user_id and listing_id in the CBV CreateView, as the information from the listing_detail page (including these two fields) is not passed.
Accounts Models.py
class ProfileReviews(models.Model):
user = models.ForeignKey(
get_user_model(),
on_delete = models.CASCADE,
related_name = 'reviews'
)
listing_item_id = models.UUIDField()
review = models.TextField(max_length = 1000)
rating = models.IntegerField()
reviewer = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
)
def __str__(self):
return self.review
Listings Model.py
class Listing(models.Model):
item_id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False
)
lender = models.ForeignKey(
get_user_model(),
on_delete = models.CASCADE,
related_name = 'lender'
)
item_name = models.CharField(max_length=200)
def __str__(self):
return self.item_name
def get_absolute_url(self):
return reverse('listing_detail', args=[str(self.item_id)])
Listings views.py
class CreateListingReview(CreateView):
model = ProfileReviews
template_name = 'listings/create_review.html'
fields = ['review', 'rating']
context_object_name = 'create_listing_review'
success_url = reverse_lazy('listing_detail')
def form_valid(self, form):
form.instance.reviewer = self.request.user
form.instance.user = self.request.POST.get('lender')
form.instance.listing_item_id = self.request.POST.get('item_id')
return super().form_valid(form)
I think I need to pass the user and the listing_item_id from the lender and item_id field in the Listing model using form_valid, but am not sure how to pass the specific information from a particular listing.
I'm in the process of making a Recipe Book. For some reason, whenever I try to pull up a recipe from the DB to edit it, I keep getting an error where it can't find the recipe I've specified. I'm using slugs, and my logic is that I'm going from a detailView where I've already pulled up the db information, to an updateView. I'm attempting to pass the recipe object I already pulled from the detailView to the updateView, but when I do, it keeps telling me that it can't find the recipe specified.
views.py:
The base views I'm calling here are only providing a default post method for handling a search so that I don't have to put it in for every view I create so I have some code reusability
class RecipeDetailView(BaseDetailView):
model = Recipe
template_name = 'RecipeBook/recipe_detail.html'
context_object_name = 'recipe_view'
queryset = None
slug_field = 'slug'
slug_url_kwarg = 'slug'
def get_context_data(self, *args, **kwargs):
context = super(RecipeDetailView, self).get_context_data()
recipe = self.object
recipe.ingredients = recipe.ingredients_list.split('\n')
context['recipe'] = recipe
return context
class RecipeEditView(BaseUpdateView):
model = Recipe
template_name = 'RecipeBook/edit_recipe.html'
context_object_name = 'recipe_edit'
queryset = None
slug_field = 'slug'
slug_url_kwarg = 'slug'
form_class = RecipeForm
def get_context_data(self, *args, **kwargs):
context = super(RecipeEditView, self).get_context_data()
recipe = self.object
print(recipe.name)
recipe.ingredients = recipe.ingredients_list.split('\n')
recipe.categories_list = ""
categories = Category.objects.filter(recipe=recipe)
for category in categories:
if category != categories[-1]:
recipe.categories_list += (category + ", ")
else:
recipe.categories_list += category
recipe_edit_form = RecipeForm(initial={'name': recipe.name, 'ingredients_list': recipe.ingredients,
'directions': recipe.directions, 'prep_time': recipe.prep_time,
'cook_time': recipe.cook_time, 'servings': recipe.servings,
'source': recipe.source, 'category_input': recipe.categories_list})
context['recipe'] = recipe
context['recipe_edit_form'] = recipe_edit_form
return context
models.py:
class Recipe(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, default="")
ingredients_list = models.TextField(default="")
servings = models.IntegerField(default=0, null=True, blank=True)
prep_time = models.IntegerField(default=0, null=True, blank=True)
cook_time = models.IntegerField(default=0, null=True, blank=True)
directions = models.TextField(default="")
source = models.CharField(max_length=100, default="", null=True, blank=True)
categories = models.ManyToManyField(Category, blank=True)
slug = models.CharField(max_length=200, default="")
def __str__(self):
return self.name
urls.py
# ex: /Recipes/Grilled_Chicken/
path('Recipes/<slug>/', views.RecipeDetailView.as_view(), name='view_recipe'),
path('Recipes/<path:slug>/', views.RecipeDetailView.as_view(), name='view_recipe'),
# ex: /Recipes/edit/Steak/
path('Recipes/edit/<slug>/', views.RecipeEditView.as_view(), name='edit_recipe'),
path('Recipes/edit/<path:slug>/', views.RecipeEditView.as_view(), name='edit_recipe'),
link in recipe_detail.html:
Edit Recipe
I've been going nuts trying to figure it out. By everything that I have in here, the recipe that I'm pulling up in the detailView should be able to be passed to the editView, but every time I try to open up the edit_recipe page, it keeps telling me that it can't find the recipe specified. The URL that it generates shows the proper slug and link that it should though. I don't know what I'm missing at this point...
Try this way:
Edit Recipe
I ended up having to go back through and change the view to be a DetailView. That was the only way I could get the recipe instance to be pushed through. There's something very specific about using an update view with models that isn't very clear...
Once I changed to a DetailView, the page would populate with the form initialized to the recipe values. I could then make tweaks to make sure everything worked from there.
Thanks for those who responded, it at least got my brain working in a different direction to get this figured out.
So, I'm new to Django and after hours of searching / trying different things I can't figure this out.
I have a form that submits my components state to the api, adds the values to the database, and then displays them in a table. Everything is getting into the database except for the "projects" field. When I look in the React Dev tools, the value I expect is in my state.
project: 3
But after I submit the form the returned value shows as null.
project: null
I'm really not sure why the value is null.
Here is my models.py
class Completed(models.Model):
completed = models.BooleanField(default=False)
url = models.CharField(max_length=100)
handle = models.CharField(max_length=30)
filename = models.CharField(max_length=100)
size = models.IntegerField()
source = models.CharField(max_length=50)
uploadId = models.CharField(max_length=50)
originalPath = models.CharField(max_length=50)
owner = models.ForeignKey(
User, related_name="completed", on_delete=models.CASCADE, null=True)
project = models.ForeignKey(
Project, related_name="projectId", on_delete=models.CASCADE, null=True)
uploadDate = models.DateTimeField(auto_now_add=True)
Here is the Project model
class Project(models.Model):
projectCode = models.CharField(max_length=10)
projectName = models.CharField(max_length=100)
user = models.ForeignKey(
User, related_name="projects", on_delete=models.CASCADE, null=True)
editor = models.ForeignKey(
User, on_delete=models.CASCADE, null=True)
creationDate = models.DateTimeField(auto_now_add=True)
completedDate = models.DateTimeField(null=True, blank=True)
dueDate = models.DateTimeField(null=True, blank=True)
def __str__(self):
return f"{self.projectName}"
Here are the serializers
# Project Serializer
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = [
'id',
'projectCode',
'projectName',
'user'
]
depth = 1
# Completed Serializer
class CompletedSerializer(serializers.ModelSerializer):
# project = ProjectSerializer()
class Meta:
model = Completed
fields = [
'completed',
'url',
'handle',
'filename',
'size',
'source',
'uploadId',
'originalPath',
'owner',
'project',
'uploadDate'
]
depth = 1
I tried adding the ProjectSerializer into the CompletedSerializer but it gave me a 400 bad-request error.
And here is the viewset
class CompletedViewSet(viewsets.ModelViewSet):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = CompletedSerializer
def get_queryset(self):
queryset = self.request.user.completed.all().filter(completed=True)
return queryset
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Can anyone tell me what I'm doing wrong here?
You're using depth=1 on your serializers. That means you'll have nested serializer for your ForeignKey. By default, nested serializers are read-only. If you want a writable nested serializer, you have to uncomment your # project = ProjectSerializer() on CompletedSerializer and pass all the data for the creation of a project instance. I don't think this is what you want. If I've understood well, you only want to reference an existing project. So, the best way to accomplish that, in my opinion, is removing the depth on your CompletedSerializer. If you need the project when you list or retrieve a Completed instance, go with a different serializer based on the request action. Here more details about writable-nested serializers https://www.django-rest-framework.org/community/3.0-announcement/#writable-nested-serialization.
For using different serializers, this is a reference. Add this code in your ViewSet:
def get_serializer_class(self):
if self.action == 'list' or self.action == 'retrieve':
return CompletedNestedSerializer
return CompletedSerializer
I am using django-bootstrap-modal-forms and it works perfectly as in documentation when using fields form my model. Some of the fields are ForeignKeys and they are displayed properly for user to select a value from database table that is referenced by the key, but instead of that I need to put username of the current user.
I tried to change how the CreateView class handles fields, but with no luck. Probably doing something wrong.
models.py
class userSchoolYear(models.Model):
user_in_school = models.ForeignKey(get_user_model(), null=True, on_delete=models.CASCADE)
school = models.ForeignKey(sifMusicSchool, on_delete=models.CASCADE)
school_year = models.ForeignKey(sifSchoolYear, on_delete=models.CASCADE)
school_year_grade = models.CharField(max_length=4, choices=IZBOR_RAZREDA, default=None, null=True)
user_instrument = models.ForeignKey(instType, on_delete=models.CASCADE, default=None, null=True)
user_profesor = models.ForeignKey(profSchool, on_delete=models.CASCADE, default=None, null=True)
views.py
class SchoolYearCreateView(BSModalCreateView):
template_name = 'school_year.html'
form_class = SchoolYearForm
success_message = 'Success!'
success_url = reverse_lazy('school')
def __init__(self, *args, **kwargs):
self.form_class.user_in_school = 'johnny' ### HERE
print(user.username)
super().__init__(*args, **kwargs)
forms.py
class SchoolYearForm(BSModalForm):
class Meta:
model = userSchoolYear
fields = '__all__'
Thanks to the author Uroš Trstenjak I was able to find a solution. I was wrong trying to set field values from views.py, instead it should be done in forms.py. So, basically I had to write a init for the form and alter fields values. Uroš pointed out that at from level I can get current user from self.request.user and it did work.
I keep getting the following integrityError when trying to implement the django-multiupload app:
null value in column "post_id" violates not-null constraint
DETAIL: Failing row contains (9, post_images/1899987_718836594813781_835045800_n.jpg, , null, null, null).
I am trying to allow my users to upload multiple pictures to a post. I think where I am going wrong is that it may be saving before being able to get the post id. In my views I have changed the form_valid which may also be effecting it.
Ideally I would like to pass the picture_author and project instances to each image as well, but to get started I have put them = null to figure out how to pass just the post instance at the moment.
Im new to coding & django, so any help in trying to solve it would be much appreciated!
models:
class ProjectPost(models.Model):
project = models.ForeignKey(UserProject)
title = models.CharField(max_length=100)
post_overview = models.CharField(max_length=1000)
date_created = models.DateTimeField(auto_now_add=True)
post_views = models.IntegerField(default=0)
post_likes = models.IntegerField(default=0)
post_author = models.ForeignKey(User, null=True)
class PostImages(models.Model):
post_picture = models.FileField(upload_to='post_images', blank=True)
post = models.ForeignKey(ProjectPost)
picture_description = models.CharField(max_length=100)
picture_author = models.ForeignKey(User, null=True)
project = models.ForeignKey(UserProject, null=True)
forms
class ProjectPostForm(forms.ModelForm):
class Meta:
model = ProjectPost
fields = ('title', 'post_overview')
files = MultiFileField(min_num=1, max_num=20, max_file_size=1024*1024*5)
def save(self, commit=True):
instance = super(ProjectPostForm, self).save(commit)
for each in self.cleaned_data['files']:
PostImages.objects.create(post_picture=each, post=instance)
return instance
views
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
self.object = form.save(commit=False)
project = UserProject.objects.get(slug=self.kwargs["slug"])
self.object.project = project
form.instance.post_author = self.request.user
self.object.save()
return super(NewPost, self).form_valid(form)
def get_success_url(self):
project_username = self.request.user.username
project_slug = self.object.project.slug
return reverse('user_project', kwargs={'username':project_username, 'slug': project_slug})
You have to save the post before you can add images to it. You need to explicitly tell the get_or_create what the post_id is supposed to be before an image will be saved, and I dont think you're doing that in your code.