Django - Update a model field through a form - python

I have this model which represents the values users submit through a form when making an applying for sponsorship.
class ApplicationData(models.Model) :
class Status(models.TextChoices):
REJECTED = 'Rejected'
PENDING = 'Pending'
ACCEPTED = 'Accepted'
"""Define table for Applications in database"""
user = models.ForeignKey(User, on_delete=models.DO_NOTHING)
organisationName = models.CharField(max_length=200, null=True, blank=True)
projectTitle = models.CharField(max_length=200, null=True, blank=True)
CH_OSCR_number = models.CharField(max_length=20, unique=True, blank=True, null=True, )
projectDesc = models.TextField(max_length=300, null=True, blank=True)
userGroupDesc = models.TextField(max_length=300, null=True, blank=True)
learningOpp = models.TextField(max_length=300, null=True, blank=True)
keyPartnersWork = models.TextField(max_length=300, null=True, blank=True)
projImpactClimate = models.TextField(max_length=300, null=True, blank=True)
projSupportLocBus = models.TextField(max_length=300, null=True, blank=True)
proContribution = models.TextField(max_length=300, null=True, blank=True)
length = models.IntegerField(null=True, blank=True)
application_complete = models.BooleanField(default=False)
date_of_application = models.DateField(null=True, blank=True)
reviewed = models.BooleanField(default=False)
app_status = models.TextField(choices = Status.choices, default = Status.PENDING)
On the system there are 3 different user types. Once an applicant submits an application the staff users can view the submitted application details on a dedicated page. What I'm trying to do is to add a form on that page where the staff will have the ability to change the status of the application (app_status on the model).
Here are my efforts:
Here the is the form to update the field.
class UpdateAppStatus(forms.ModelForm):
class Meta:
model = ApplicationData
fields = ('app_status',)
labels = {'app_status':'Change application status?'}
This is the view - it displays the application details and im also trying to add this functionality to update the status.
def view_application_status(request, id):
admin = True if request.user.is_superuser else False
application = ApplicationData.objects.get(id = id)
application_form = ApplicationForm(instance=application)
comments = Comments.objects.filter(application = application)
if request.user.is_staff:
statusform = UpdateAppStatus()
if request.method == 'POST':
satusform = UpdateAppStatus(request.POST)
if satusform.is_valid():
statusform.save()
else:
print(satusform.errors)
return render(request, 'fund/application_view.html', {'application':application, 'application_form' :application_form, 'comments':comments, 'admin':admin,'statusform':statusform})
print(comments)
for comment in comments:
print(comment.comment)
else:
return render(request, 'fund/application_view.html', {'application':application, 'application_form' :application_form, 'comments':comments, 'admin':admin})
And here the is html part:
<main role="main" class="inner cover">
<div class="container-fluid">
<div class="cover-container d-flex h-100 p-3 mx-auto flex-column">
<h1 class="cover-heading text-center">Application Status</h1>
<div class="cover-container d-flex h-100 p-3 mx-auto flex-column">
{% if request.user.is_staff %}
<form id = "ReviewForm" method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
<label for="app_status"><strong>{{statusform.app_status.label}}</strong></label>
{{ statusform.app_status }}
<input type="submit" value="Submit" class="btn btn-primary" onclick="">
</form>
{% endif %}
</div>
So what I'm trying to achieve is for the staff to change the value of the field app_status using this form that is visible on the view application status page.
I believe the issue is in the view function but I did not manage to find a solution.

Related

Django - How to add a comment limit of 1

I currently have a comment functionality and I want to add a limit of one comment for each user but don't know how to do that.
I tought on making a 'posted' field in the user model which would be true when the user posted a comment but I don't know how to do that and most importantly if that is the better way of doing that...
class Comment(models.Model):
service = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.CharField(max_length=200, null=False, blank=True)
date = models.DateTimeField(auto_now_add=True)
def get_absolute_url(self):
return reverse('product-feedback', kwargs={'pk': self.pk})
class ProductFeedbackView(DetailView):
model = Product
template_name = 'services/product-feedback.html'
def get_context_data(self , **kwargs):
data = super().get_context_data(**kwargs)
connected_comments = Comment.objects.filter(service=self.get_object())
number_of_comments = connected_comments.count()
data['comments'] = connected_comments
data['no_of_comments'] = number_of_comments
data['comment_form'] = CommentForm()
return data
def post(self , request , *args , **kwargs):
if self.request.method == 'POST':
comment_form = CommentForm(self.request.POST)
if comment_form.is_valid():
content = comment_form.cleaned_data['content']
new_comment = Comment(content=content, author=self.request.user , service=self.get_object())
new_comment.save()
return redirect(self.request.path_info)
{% block content %}
{% if user.is_authenticated %}
<form action="" method="POST" id="main_form" class="comment_form">
<div>
<label for="comment">Type Comment here</label>
{{ comment_form.content }} {% csrf_token %} <input type="submit" value="Post"></div>
</div>
</form>
{% else %}
<h2>You need to Login to comment</h2>
{% endif %}
{% for comment in comments %}
<h3> <b>{{ comment.author }} : </b> {{ comment.content }}</h3>
{% endfor %}
{% endblock content %}
If you are really sure a user should post one and only one comment, you should use a OneToOneField. This way, the unique constraint shall be be automatically generated and the object easier to manage.
class Comment(models.Model):
service = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, related_name='comments')
author = models.OneToOneField(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.CharField(max_length=200, null=False, blank=True)
If you want the user to post only one comment per product but still allow him to write comments on as many products as he wants, you should add a constraint in the Meta class :
class Comment(models.Model):
service = models.ForeignKey(Product, on_delete=models.CASCADE, blank=True, null=True, related_name='comments')
author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True,)
content = models.CharField(max_length=200, null=False, blank=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=["service", "author"], name="One comment per user per product")
]

Can't render out user specific content

I am trying to render out model objects that are unique to a user. All methods I've found haven't worked and I've been trying to figure this out for a while now. What is supposed to happen is that you can see the jobs you've posted in the manage-jobs.html template. Also I've removed all of my failed attempts to render out user specific content, it's not that I haven't tried to do this by myself. Thanks.
models.py
class Job(models.Model):
company = models.CharField(max_length=40, null=True, verbose_name="Company/Employer")
description = models.TextField(null=True)
role = models.CharField(max_length=25)
area_of_filming = models.CharField(max_length=50, verbose_name="Area Of Filming", default="")
contact_email = models.EmailField(verbose_name='Contact Email', max_length=60, default='')
created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(Account, default='', null=True, on_delete=models.CASCADE)
def __str__(self):
return self.company
class Account(AbstractBaseUser):
email = models.EmailField(verbose_name='email', max_length=60, unique=True)
name = models.CharField(max_length=45, unique=False)
date_joined = models.DateTimeField(verbose_name='date joined', auto_now_add=True)
last_login = models.DateTimeField(verbose_name='last login', auto_now=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_employee = models.BooleanField(default=True, verbose_name='Are you using FilmLink as an employee?')
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'is_employee']
objects = MyAccountManager()
class Meta:
permissions = [
("post_jobs", "Can post jobs"),
]
def __str__(self):
return self.name
def has_perm(self, perm, obj=None):
return True
def has_perms(self, perm):
return True
def has_module_perms(self, app_label):
return True
#property
def is_staff(self):
return self.is_admin
views.py
#login_required(login_url='login')
def manage_jobs(request):
if request.user.is_employee:
return redirect('home')
else:
form = JobForm(request.POST)
if form.is_valid():
form.save()
context = {"form":form}
return render(request, 'employer/manage-jobs.html', context)
manage-jobs.html
<button id="myBtn">Post A Job</button>
<div id="main"></div>
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close">&times</span>
<form action="" method="POST">
{% csrf_token %}
<div id="company-container">
<p>Employer Name</p>
<p id="employer">{{form.company}}</p>
</div>
<div id="role-container">
<p>Role (e.g. Actor, Director)</p>
<p id="role">{{form.role}}</p>
</div>
<div class="area-of-filming-container">
<p>Area Of Production/Filming</p>
<p id="area-of-filming">{{form.area_of_filming}}</p>
</div>
<div id="contact-email-container">
<p>Contact Email</p>
<p id="contact-email">{{form.contact_email}}</p>
</div>
<div id="description-container">
<p>Description Of Job</p>
<p id="description">{{form.description}}</p>
</div>
<button type="submit" id="post-job">Publish Job</button>
</form>
</div>
</div>
You said that this should render out "jobs". This is only going to render out one job but I am going to assume you intend to solve that after you can actually see content on your screen.
I think the problem is to do with your frontend form.
Note that on your views.py function manage_jobs, that if the user is not an employee you create a form by POST, but the frontend is not connecting to that request.POST.
I am not sure how your urls.py file looks, but you want to link to that views.py call from your form.
In your <form action="{% url 'manage_jobs' %}" method="POST">
This may not be your solution, but I would check out this anyway.
Hope you can figure it out!

How to save foreign key data using forms.py?

urls.py
...
path('restaurant/menu/', r_view.Menu, name='menu'),
...
menu.html
<form method="POST" id="menuForm" autocomplete="off" action="" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-inline">
<div class="form-group mb-4">
{{ form.item|as_crispy_field }}
</div>
<div class="form-group mb-4">
{{ form.itemImage|as_crispy_field }}
</div>
<div class="form-group mb-4">
{{ form.price|as_crispy_field }}
</div>
<div class="form-group mb-4">
{{ form.category|as_crispy_field }}
</div>
</div>
<button type="submit" class="col-md-12 myBtn">Submit</button>
</form>
views.py
def Menu(request, restaurantID):
restaurant = get_object_or_404(Restaurant_Account, restaurantID=restaurantID)
form = MenuForm()
if request.method == 'POST':
form = MenuForm(request.POST, request.FILES)
if form.is_valid():
instance = form.save(commit=False)
instance.restaurant = restaurant
instance.save()
messages.success(request, "Saved successfully!")
return redirect('r_index')
context = {'form':form}
return render(request, 'restaurant/menu.html', context)
forms.py
class MenuForm(forms.ModelForm):
restaurantID = Restaurant_Account.objects.filter(restaurantID='restaurantID')
item = forms.CharField(required=True)
itemImage = forms.ImageField(required=False, label='Item image')
price = forms.DecimalField(required=True)
category = forms.ChoiceField(choices=CATEGORY)
class Meta:
model = Menu
fields = ('item', 'itemImage', 'price', 'category')
models.py
class Restaurant(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
is_restaurant = models.BooleanField(default=True)
restaurantID = models.AutoField(primary_key=True)
name = models.CharField(max_length=200)
isActive = models.BooleanField(default=True)
image = models.ImageField(upload_to='images/', blank=True)
website = models.URLField(blank=True, unique=False)
country = models.CharField(max_length=50)
def __str__(self):
return self.user.username
class Menu(models.Model):
menuID = models.AutoField(primary_key=True)
restaurantID = models.ForeignKey(Restaurant_Account, on_delete=models.CASCADE, default=None)
item = models.CharField(max_length=100)
itemImage = models.ImageField(upload_to='images/', blank=True)
price = models.DecimalField(max_digits=6, decimal_places=2)
category = models.CharField(
max_length=20,
choices=[('', 'Choose category'),('Appetizer', 'Appetizer'),('Entree', 'Entree'),('Drink', 'Drink'),('Dessert', 'Dessert'), ('Side', 'Side')])
def __str__(self):
return self.item
I'm new to Django.
I made a form for saving menu data. If the user fills the form and click the submit button every data should be saved in the Menu table. I have no idea how to save restaurantID, which is a foreign key that refers to the Restaurant table, automatically. (By automatically I mean without the user entering input) Can somebody help me with this?
You haven't need to do all these things, if you have made restaurantID a foreign key while defining the model that is Menu, django itself handles it.
Below code might work for you:
forms.py
class MenuForm(forms.ModelForm):
item = forms.CharField(required=True)
itemImage = forms.ImageField(required=False, label='Item image')
price = forms.DecimalField(required=True)
category = forms.ChoiceField(choices=CATEGORY)
class Meta:
model = Menu
fields = ('item', 'itemImage', 'price', 'category')
views.py
def menu(request):
if request.method == 'POST':
form = MenuForm(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.success(request, "Saved successfully!")
return redirect('r_index')
else:
form=MenuForm()
return render(request, 'restaurant/menu.html', {'form':form})
optional: You also don't need to write this line menuID = models.AutoField(primary_key=True) in Menu model, as django makes id column by default for AutoField.
Update:
Make your models.py in this way:
MY_CHOICES=[
('', 'Choose category'),
('Appetizer', 'Appetizer'),
('Entree', 'Entree'),
('Drink', 'Drink'),
('Dessert', 'Dessert'),
('Side', 'Side')
]
class Restaurant(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
is_restaurant = models.BooleanField(default=True)
name = models.CharField(max_length=200)
isActive = models.BooleanField(default=True)
image = models.ImageField(upload_to='images/', blank=True)
website = models.URLField(blank=True, unique=False)
country = models.CharField(max_length=50)
def __str__(self):
return self.user.username
class Menu(models.Model):
restaurant= models.ForeignKey(Restaurant, on_delete=models.CASCADE, default=None)
item = models.CharField(max_length=100)
itemImage = models.ImageField(upload_to='images/', blank=True)
price = models.DecimalField(max_digits=6, decimal_places=2)
category = models.CharField(
max_length=20,
choices=MY_CHOICES)
def __str__(self):
return self.item
Remove your AutoField, django by default make id which is primary key.
Don't forget to run makemigrations and migrate, after doing this, if it still gives error, so comment your all models and then run.

How to compare data from two models in Modelform Django

I'm creating an ebay like auction site where users can bid on an item they like and if the bid is higher than the last bid, the bid amount displayed would update to the newest bid.
I want to run validation on the modelform to compare the first amount (that the person that created the listing) inputted with the last bid (that the new user inputted).
The problem is that they are in two different models and I'm not sure how to validate both without an error
FORMS.PY
class BidForm(forms.ModelForm):
class Meta:
model = Bids
fields = ['new_bid']
labels = {
'new_bid': ('Bid'),
}
def clean_new_bid(self):
new_bid = self.cleaned_data['new_bid']
current_bid = self.cleaned_data['current_bid']
if new_bid <= current_bid:
error = ValidationError("New bid must be greater than the previous bid")
self.add_error('new_bid', error)
return new_bid
MODELS.PY
class Auction(models.Model):
title = models.CharField(max_length=25)
description = models.TextField()
current_bid = models.IntegerField(null=False, blank=False)
image_url = models.URLField(verbose_name="URL", max_length=255, unique=True, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(Category, max_length=12, null=True, blank=True, on_delete=models.CASCADE)
is_active = models.BooleanField(default=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
class Meta:
ordering = ['-created_at']
class Bids(models.Model):
auction = models.ForeignKey(Auction, on_delete=models.CASCADE, related_name='bidding', null=True)
user = models.ForeignKey(User, on_delete=models.PROTECT, related_name='bidding')
new_bid = models.IntegerField()
done_at = models.DateTimeField(auto_now_add=True)
VIEWS.PY
#login_required
def make_bid(request, listing_id):
auction = Auction.objects.get(pk=listing_id)
user = request.user
if request.method == 'POST':
bid_form = BidForm(request.POST)
if bid_form.is_valid():
new_bid = request.POST['new_bid']
current_price = Bids.objects.create(
listing_id = listing_id,
user = user,
new_bid = new_bid
)
messages.success(request, 'Successfully added your bid')
return HttpResponseRedirect(reverse("listing_detail", args=(listing_id,)))
else:
bid_form = BidForm(request.POST)
return render(request, 'auctions/details.html', {"bid_form": bid_form})
return render(request, 'auctions/details.html', bid_form = BidForm())
DETAILS.HTML
<p>{{ detail.description }}</p>
<hr>
<p>Current price: ${{detail.current_price}}</p>
<form action="{% url 'make_bid' detail.id %}" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
{{ form.errors }}
{{ bid_form }}
<input type="submit" class="btn btn-primary btn-block mt-3" value="Place bid">
</form>
I'm having this error
KeyError at /make_bid/2
'current_bid'
Request Method:
POST
Request URL:
http://127.0.0.1:8000/make_bid/2
I'm sure its because I'm trying to compare two different models, but don't know a better way to do this. Could you please direct me or is there a better way to run this bid validation process?

Additional help text to form in Django

When I create or edit model CV, I need to input some data in birth_date field. It's working, but I want to add some additional text to define some date format like (yyyy-mm-dd). I'm using cripsy forms for better look of forms. How can I add this help text ?
my code:
template.html
{% block profile %}
<div class="jumbotron">
<h2>Edit your basic informations</h2>
<hr>
<form method="POST" class="post-form" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="save btn btn-default">Save</button>
</form>
</div>
{% endblock %}
models.py
class Cv(models.Model):
author = models.ForeignKey('auth.User')
name = models.CharField(max_length=25, null = True)
surname = models.CharField(max_length=25, null = True)
city = models.CharField(max_length=100, blank=True)
birth_date = models.DateField(blank=True, null=True)
email = models.EmailField(max_length=50, null=True)
main_programming_language = models.CharField(max_length=15, null = True)
specialization = models.CharField(max_length=30, blank=True, null=True)
interests = models.TextField(blank=True, null=True)
summary = models.TextField(blank=True, null=True)
#thumbnail = models.FileField(upload_to=get_upload_file_name, blank=True)
#property
def age(self):
return int((datetime.datetime.now().date() - self.birth_date).days / 365.25 )
def zapisz(self):
self.save()
def __str__(self):
return self.surname.encode('utf-8')
forms.py
class CvForm(forms.ModelForm):
class Meta:
model = Cv
fields = ('name', 'surname', 'city', 'birth_date', 'email', 'main_programming_language', 'specialization', 'interests', 'summary',)
views.py
#login_required
def new_cv(request):
if request.method == "POST":
form = CvForm(request.POST, request.FILES)
if form.is_valid():
cv = form.save(commit=False)
cv.author = request.user
cv.save()
return redirect('proj.views.cv_detail', pk=cv.pk)
else:
form = CvForm()
return render(request, 'new_cv.html', {'form': form})
Can add help_text to your model fields:
birth_date = models.DateField(blank=True, null=True, help_text="format (yyyy-mm-dd)")
see more Django Model and Form docs.
You can also use external library JQuery Tooltip too.

Categories