Django - Pass Object ID after redirect - python

Apologies if the question title is wrong but I am unsure of exactly what I need to do. I am building a scrum app where users can create user stories and add them to sprints. I want to have two separate ways of creating stories. If the user is creating a story from the backlog, the story that has been created will not be assigned to a sprint, but the user can also add a story from a sprint's detail screen. I want to make it so that if they add a story from a specific sprint's page, that story's sprint field is populated with that sprint.
The way I have tried to go about this is by creating two different functions in my views.py file and two separate templates. I am able to create the story without relating to a sprint as intended, but I cannot think of a way to get the id of the sprint where I navigated from to get to the new template.
For reference I have included my function for creating stories without relating to a sprint:
def create_story(response, id=None):
user = response.user
if response.method == "POST":
form = CreateNewUserStory(response.POST)
if form.is_valid():
name = form.cleaned_data["name"]
description = form.cleaned_data["description"]
status = form.cleaned_data["status"]
assignee = form.cleaned_data["assignee"]
estimate = form.cleaned_data["estimate"]
userstory = UserStory(name=name, description=description, status=status, assignee=assignee, estimate=estimate)
userstory.save()
response.user.userstories.add(userstory)
return HttpResponseRedirect("/backlog")
else:
form = CreateNewUserStory()
return render(response, "main/create_story.html", {"form": form})
If any more information or code is required please let me know, thank you!

You can define 2 URLs, one with an optional sprint ID:
urls.py:
path('story/<int:sprint_id>/add/', views.create_story, name='create_story_for_sprint'),
path('story/add/', views.create_story, name='create_story_for_backlog'),
views.py:
def create_story(request, sprint_id=None):
...
form = CreateNewUserStory(response.POST, initial={"sprint_id": sprint_id})
if form.is_valid():
...
sprint_id = form.cleaned_data["sprint_id"]
create_story.html:
<form action="..." method="POST">
<input type="hidden" name="sprint_id" value="{{ sprint_id|default_if_none:'' }}">
...
Then, in your sprint page, use
Add Story
and in your backlog page:
Add Story

Related

Deleting FlaskForm Fields depending on userinput in other forms

I have 3 forms with Checkboxes to configure the desired form (Final_Form). After the user chooses the desired fields (in form1, form2 and form3), i want to delet all fields that are not required in the final form and render the final form. The reason for that is, that i have 3 Subkategories with around 12 possible values, in each form (form1-form3) the user can choose one ore more subkategories. The subcategories are standardized and are used to describe a clinical incident. The users wished to have the subcategories (1-3; form1-form3) seperated and always with an example (right-side of the screen in an anther bootstrap col).
The finalform is than a combination of the the subcategories that matches best to describe the clinical incident. All fields in the Final_Form are TextAreaFields. The Input for the TextAreaFields is stored in a sqlite-db.
Here is how i tried it:
app.py:
if request.method == 'POST' and form1.form1Submit.data:
OnePointOne = form1.OnePointOne.data
if not OnePointOne:
del Final_Form.OnePointOne
return render_template('Form2.html', form2 = form2)
if request.method == 'POST' and form2.form2Submit.data:
TwoPointTwo = form2.TwoPointTwo.data
if not TwoPointTwo:
del Final_Form.TwoPointTwo
return render_template('Form3.html', form3 = form3)
if request.method == 'POST' and form3.form3Submit.data:
ThreePointThree = form3.ThreePointThree.data
if not ThreePointThree:
del Final_Form.ThreePointThree
return render_template('Final.html', Final_Form = Final_Form)
forms.py:
class form1(FlaskForm):
OnePointOne = BooleanField('Effect')
form1Submit = SubmitField('Submit Category')
class form2(FlaskForm):
TwoPointTwo = BooleanField('Measure')
form2Submit = SubmitField('Submit Category')
class form3(FlaskForm):
ThreePointThree = BooleanField('Result')
form3Submit = SubmitField('Submit Category')
class Final_Form(FlaskForm):
OnePointOne = TextAreaField('Example Effect')
TwoPointTwo = TextAreaField('Example Measure')
ThreePointThree = TextAreaField('Example Result')
Final_FormSubmit = SubmitField('Submit incident')
The problem is, that the formfields of the Final_Form objects dont get deleted (only inside the if statements). I am very thankful for every hint or explanation.
As you are showing three separate pages, there are three separate requests.
You Final_Form object cannot be simply kept between these requests.
I don't fully understand why you configure your third form this way, it would be helpful to explain your use-case for better advice.
Without more information, I'm thinking of some ways to do this:
You make it one page/request, where you go from form to form using AJAX.
You make it one page with all forms, controlling visualisation with JS + CSS
You save your desired value somewhere
probably you can keep it in cookie (session object)
or in database, if that makes sense in your context
Also, please include whole code of this function - it's not clear how you create those forms you use.

How to replace Foreign Key with Many To Many in Django models without broking whole app?

I want to change my Foreign Key to Many To Many field to let the user select multiple categories in a dropdown list.
This is what I already have. After I change Foreign Key to Many To Many I'm getting milion errors, I have to get rid of on_delete=models.CASCADE which is a core of my app. What can I do? Which way should I take? Maybe add another model? I'm so confused, especially when I am a Django newbie. Thank you for your help!
MODELS
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return f'{self.name}'
class Expense(models.Model):
class Meta:
ordering = ('date', '-pk')
category = models.ForeignKey(Category, null=True,blank=True, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
amount = models.DecimalField(max_digits=8,decimal_places=2)
date = models.DateField(default=datetime.date.today,db_index=True)
def __str__(self):
return f'{self.date} {self.name} {self.amount}'
The clue of the application is to let the user create a category e.g "PC". Then add some expenses like "GPU", "CPU" etc... and let the user link it to the "PC" category. And when the user wants to delete certain categories, all the expenses linked to it, gonna be deleted too. And this is the thing I have already did. BUT NOW I want to let the user search the main table of expenses by multiple categories. And here comes my problem, I don't have a clue how to do it and keep the whole application in one piece with all the functionalities.
SCREENSHOTS:
Categories View with just added PC category
Expense Add View
I don't think there is a simple answer to your question, but here are some resources that might help. First, I don't think you should change your models. From the way you described your application, I think a foreign key model with on_delete=CASCADE is good. The basic idea here is that you need to change your list view function so that it performs a query of your database. Also modify your template.html to include a search bar.
https://github.com/csev/dj4e-samples/tree/master/well
https://www.dj4e.com/lessons/dj4e_ads4
Modify Your List View To Allow The Searching
This is an example of a list view that allows you to search for a single term, and returns anything in the database that matches from any field. This isn't what you want to do exactly, but if you can get this working then you can modify the search conditions for your specific application. What is going on in the code below is that instead of return every item in my Ad table in my SQL database, I filter it based on the search. Then, I pass "ad_list" to the template view. Since I already filtered ad_list based on the search, in the template view it will only list the items that match. This is based on the DJ4E course, and you can watch the video there to get an idea of how he implements the search bar better.
from ads.models import Ad
from django.views import View
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy, reverse
from django.http import HttpResponse
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.contrib.humanize.templatetags.humanize import naturaltime
from ads.utils import dump_queries
from django.db.models import Q
class AdListView(ListView):
# By convention:
template_name = "ads/ad_list.html"
def get(self, request) :
strval = request.GET.get("search", False)
if strval :
# Simple title-only search
# objects = Ad.objects.filter(title__contains=strval).select_related().order_by('-updated_at')[:10]
# Multi-field search
query = Q(title__contains=strval)
query.add(Q(text__contains=strval), Q.OR)
objects = Ad.objects.filter(query).select_related().order_by('-updated_at')[:10]
else :
# try both versions with > 4 posts and watch the queries that happen
objects = Ad.objects.all().order_by('-updated_at')[:10]
# objects = Ad.objects.select_related().all().order_by('-updated_at')[:10]
# Augment the post_list
for obj in objects:
obj.natural_updated = naturaltime(obj.updated_at)
ctx = {'ad_list' : objects, 'search': strval}
retval = render(request, self.template_name, ctx)
dump_queries()
return retval;
Modify Your Template.html to include a search bar
<form>
<input type="text" placeholder="Search.." name="search"
{% if search %} value="{{ search }}" {% endif %}
>
<button type="submit"><i class="fa fa-search"></i></button>
<i class="fa fa-undo"></i>
</form>
PS, I think you can answer your own question better when you figure it out, so help others and post it!

Django-taggit tag value retrieval and formatting failing

I am trying to implement a tagging process for profiles so you can add your hobbies for example.
I have chosen django-taggit as it seemed quite simple and does what I need it to, plus don't really know how to do it myself from scratch.
I have managed to make it work to some extent but I am having issues with 3 things:
Not really sure what's the best way to control the form field for these tags as I generate the form automatically with widget adjustments in meta function of the form, but it might work fine after resolving the below two issues.
When there is no data for the field hobbies (tags) the field gets populated with a single tag of value "[]" as per below image.
When I add a tag of "music" and submit the form after I reload the page I get this "[]" as per image. I assumed this will be dealt with by the library, but I cannot see another similar scenario online.
When I try adding another tag of "games" and save and reload, the below happens. The initial value gets wrapped again.
My model is:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
hobbies = TaggableManager()
My form is:
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['hobbies',]
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
self.fields['hobbies'].widget = forms.TextInput()
self.fields['hobbies'].widget.attrs['data-role'] = "tagsinput"
self.fields['hobbies'].widget.attrs['class'] = "form-control"
self.fields['hobbies'].required = False
My view function is:
if request.method == 'POST':
user_profile = UserProfile.objects.get(user=request.user)
form = UserProfileForm(request.POST, instance=user_profile)
print(form)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
print("Form valid")
form.save_m2m()
Using:
<script src="/static/js/tagsinput.js"></script>
<link rel="stylesheet" href="{% static 'css/tagsinput.css' %}" />
I had this exact same problem.
One solution is to apply the data-role="tagsinput" AFTER you turn a list of tags into a comma-separated string for the form.
Here is that solution:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, **kwargs):
self.fields['tags'].widget.attrs['value'] = ", ".join(list(self.instance.tags.names()))
self.fields['tags'].widget.attrs['data-role'] = "tagsinput"
Output:
As you can see, there's a problem with quotes appearing around tags that are multi-word. It also causes new tags with quotes to be saved to the database.
If double-quotes didn't appear around multi-word phrases, this would be the most elegant solution. If someone solves this in the future, drop a note!
My template is this:
<div class="m-3 p-3 border">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">Save Form</button>
</form>
</div>
I know I can use a template tag to strip the extra quotes from the tag field itself, but then I'd have to go through and create all the form fields manually just to set the tags template tag.
For the time being, my solution is to simply use Javascript and just modify the Meta widgets section of the form.
FINAL ANSWER (for now):
forms.py
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
widgets = {
'tags': forms.TextInput(attrs={
"data-role": "tagsinput",
})
}
custom.js - put this script on the page that loads the form.
document.addEventListener("DOMContentLoaded", function(event) {
let tags_input = document.querySelector('#id_tags');
let tags_input_value = tags_input.value;
let new_value = [...tags_input_value.matchAll(/<Tag:\s*([\w\s]+)>/g)].map(([, m]) => m).join(', ')
tags_input.setAttribute('value', new_value);
}
So all we're doing is modifying the front-end presentation, and leaving all the backend internal forms functionality untouched.
So after quite a few (hundreds) of tests, I finally narrowed down where the issue was and tried to go around it with successful result.
It seems the data got amended into tag objects through tagsinput library I was using. Only when the "data-role" was specified as "tagsinput" in the forms.py the data would already come to html side as those objects and be shown incorrectly. So instead I wanted to keep the data clean and only apply data-role='tagsinput' in the end for visual aspect, which I did using:
var hobbiesTags = document.getElementById("id_hobbies");
if(hobbiesTags){
var att = document.createAttribute("data-role");
att.value = "tagsinput";
hobbiesTags.setAttributeNode(att);
};
And that resulted in the below. Maybe there are better ways to do this, I'm not sure, but it's a pretty clean solution. Share your alternatives.

Django. How to restrict "profile" page to only "friends"

I currently have a "profile" page that displays specific users information they have uploaded. Currently it does so by
objects.filter(user = request.user)
Im trying to figure out how I can allow a, for lack of a better description, a "friend" to view someone else's profile. Cause right now, the user who makes the request gets their own information. I believe I know how to build the ability to "friend" another user... i just dont know how to display another users info since all ive been filtering on so far is "request.user"
You can do this using Many-to-many relationships
You object should look like this
class Profile(models.Model):
friends = models.ManyToManyField(Profile)
To check whether target profile belongs to your friend you can modify your code following way:
Profile.objects.filter(friends = request.user)
I would like to share how I implemented this in a project of mine. This may be somewhat specific for how I have implemented friend relationships, but I think the main idea should be the same.
Here is the view for view_profile
def view_profile(request, username):
if request.user.username == username:
return HttpResponseRedirect(reverse('accounts:profile'))
#get the user we are looking at
person = get_object_or_404(User, username=username)
#get the userprofile
person = person.userprofile
person_friend_object, person_created = Friend.objects.get_or_create(current_user=person)
user_friends = [friend for friend in person_friend_object.users.all()]
follower_count = len(user_friends)
friend = False
context = {
'person':person,
'friend':friend,
'follower_count':follower_count,
'user_friends':user_friends,
}
if request.user.is_authenticated():
friend_object, created = Friend.objects.get_or_create(current_user=request.user.userprofile)
friends = [friend for friend in friend_object.users.all()]
if person in friends:
friend = True
else:
friend = False
context['friend'] = friend
return render(request, 'users/user_profile_view.html', context)
Then, in the template you can control what friend can see of a given user's profile with template logic. Here's a basic example:
{% if not friend %}
<p>You are not friends with this user</p><button>Add friend</button>
{% else %}
<p>You are friends with this user. Here is information about this user...(here you can show data on the user through by accessing the `person` context variable)</p><button>Unfriend</button>
{% endif %}
So everything is controlled by the friend variable which is either True or False.
There are many ways to do what you are describing, this would be just one way I believe. Hope this helps with your project.

django forms - how to update user data from previous comment when user posts a new comment

I feel like I'm really close, but not quite there yet. Please bear with me as I am very much so in the beginner stages of learning django.
I have a feature where users can comment on each blog post. I want to display the total number of comments each user has next to his name. If that user has left 4 comments on 4 different posts, I want it to show "4 total comments" next to his name on each one of his individual comments throughout my website.
I have made a model method that I put in my view, which automatically updates the total comments for each user. The only problem is that if the user has left two comments, only his latest comment will show "2 total comments". His previous one shows only "1".
My question is, how do I make the previous entry update when the user has left a new comment?
models.py
class Comment(models.Model):
...
post = models.ForeignKey(Post, related_name="comments")
user = models.ForeignKey(User, related_name="usernamee")
email = models.EmailField(null=True, blank=True)
picture = models.TextField(max_length=1000)
...
review_count = models.IntegerField(default=0)
class UserProfile(models.Model):
...
def user_rating_count(self): #This is what adds "1" to the user's total post count
user_ratings =
Comment.objects.all().filter(user_id=self.user.id).count()
user_ratings += 1
return user_ratings
views.py
#login_required
def add_comment(request, slug):
post = get_object_or_404(Post, slug=slug)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.user = request.user
comment.email = request.user.email
comment.picture = request.user.profile.profile_image_url()
comment.review_count = request.user.profile.user_rating_count() #This is where I'd like it to update, but it doesn't seem to work this way
comment.save()
return redirect('blog:post_detail', slug=post.slug)
else:
form = CommentForm()
template = "blog/post/add_comment.html"
context = {
'form': form,
}
return render(request, template, context)
template
{% for comment in post.comments.all %}
<p>{{ comment.user.first_name }} <b>{{ comment.user.last_name }}</b> {{ comment.review_count }}</p>
{% endfor %}
User comments once = FirstName LastName 1.
User comments twice, his second comment = FirstName LastName 2, but the first comment remains unchanged.
Any ideas on how to properly do this? Any help is greatly appreciated!
First i don't think you need the review_count to be a Database field. Except you plan to use it to sort (or do something that requires it to be in the Database).
From your question "django forms - how to update user data from previous comment when user posts a new comment" I believe you have an idea on why it's not working.
It's because it's a previous comment and Updating the data of the latest comment won't automatically update the previous comments (It would be disastrous if that happened by default :-) )
Anyway once you remove thereview_count and make user_rating_count a property of the UserProfile model your problem disappears.
class UserProfile(models.Model):
#property
def user_rating_count(self):
"""This is what adds "1" to the user's total post count"""
return self.usernamee.count() # Remember the `related_name` you set earlier?
Then you can use it in your templates like this
{% for comment in post.comments.all %}
<p>{{ comment.user.first_name }} <b>{{ comment.user.last_name }}</b> {{ request.user.profile.user_rating_count }}</p>
{% endfor %}
If you're bothered about the value being recalculated on each page load (You should be). Django provides a nifty decorator to cache the property in memory and reduce the load on your database (When you call the method/access the property repeatedly)
from django.utils.functional import cached_property
class UserProfile(models.Model):
#cached_property
def user_rating_count(self):
"""This is what adds "1" to the user's total post count"""
return self.usernamee.count() # Remember the `related_name` you set earlier?
If it doesn't need to be a Database field, it doesn't need to be. You can easily make it a computed property and Cache it (If you feel it's expensive to recalculate everytime and you don't really care about the "freshness" of the data).
By the way if you needed to update the Old comments (The way you wanted to initially), You would've done a batch update like this
I'm being overly verbose here:
comments = Comment.objects.filter(user_id=self.user.id)
count = comments.count()
comments.update(review_count=count)
That will update all the comments that match the filter params. But like i said i don't think this is the best approach for what you want to do.
Read up
https://docs.djangoproject.com/en/1.11/ref/utils/#django.utils.functional.cached_property
and
https://docs.djangoproject.com/en/1.11/ref/models/querysets/#update

Categories