Updating model field after input in Django - django

I am trying to update an account value model field after the user inputs a stock price and quantity from a form. Essentially the user would input a stock price and share quantity and their account balance should reflect the purchase amount. Below are images of my models.py, my forms.py, my views.py and my buy_stock.html page. Any insight as to how I can get the value to save would be very helpful. Thanks in advance. - Total newb
Views.py
from django.shortcuts import render, redirect
from .models import Stock, Bank, BuyStockModel
from .forms import StockForm, BankForm, BuyStock, Registration
from django.contrib import messages
import requests
import json
def main(request):
stocks = BuyStockModel.objects.all().order_by('-created')
form = BuyStock()
account = Bank.objects.only('account')
balance = account
if request.method == 'POST':
price = request.POST.get('price')
quantity = request.POST.get('quantity')
if price is not None:
price = float(price)
if quantity is not None:
quantity = float(quantity)
total = float(price) * float(quantity)
if balance >= total:
balance = balance - total
account.update(account = balance)
#account.save()
ticker = request.POST['ticker']
api_request = requests.get(
"https://cloud.iexapis.com/stable/stock/" +
ticker +
"/quote?token=i_should_get_a_new_one"
)
try:
api = json.loads(api_request.content)
except Exception as e:
api = "Error..."
context = {
'api': api,
'form' : form,
'stocks' : stocks,
"account" : account
}
return render(request, 'buy_stock.html', context)
else:
context = {'form' : form, 'stocks' : stocks}
return render(request, 'buy_stock.html', context)
def buy_stock(request):
print('buy_stock')
if request.method == 'GET':
form = BuyStock()
stocks = BuyStockModel.objects.all().order_by('-created')
output = {'form' : form, 'stocks' : stocks}
return render(request, 'buy_stock.html', output)
elif request.method == 'POST':
form = BuyStock(request.POST or None)
if form.is_valid():
form.save()
name = form.cleaned_data['name']
price = form.cleaned_data['price']
quantity = form.cleaned_data['quantity']
form = BuyStock()
return redirect('buy_stock')
return render(request, 'buy_stock.html', {
'form' : form,
'name' : name,
'price' : price,
'quantity' : quantity
})
Models.py
from django.db import models
class Stock(models.Model):
ticker = models.CharField(max_length = 10)
def __str__(self):
return self.ticker
class Bank(models.Model):
account = models.DecimalField(
max_digits = 15, default = 30000.0, decimal_places = 0,
editable = True
)
def __str__(self):
return self.account
class BuyStockModel(models.Model):
name = models.CharField(max_length = 100)
option = models.CharField(max_length = 10, default = 'buy')
price = models.DecimalField(
max_digits = 15, decimal_places = 2, default = 0
)
quantity = models.DecimalField(
max_digits = 15, decimal_places = 0, default = 0
)
total_value = models.DecimalField(
max_digits = 15, default = 1, decimal_places = 0,
editable = True
)
created = models.DateTimeField(auto_now_add = True)
def __str__(self):
return self.price
def __str__(self):
return self.quantity
def calc_total(self):
amount = (self.price * self.quantity)
return amount
def save_total(self):
self.total_value = self.calc_total()
super(BuyStockModel, self).save()
Forms.py
from django import forms
from .models import Stock, Bank, BuyStockModel
from django.contrib.auth import login, authenticate
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class Registration(UserCreationForm):
email = forms.EmailField()
class Meta:
model = User
fields = ["username", "email", "password1", "password2"]
class StockForm(forms.ModelForm):
class Meta:
model = Stock
fields = ["ticker"]
class BankForm(forms.ModelForm):
class Meta:
model = Bank
fields = ["account"]
class BuyStock(forms.ModelForm):
class Meta:
model = BuyStockModel
fields = ["name", "price", "quantity"]
widgets = {
'name' : forms.TextInput(attrs = {'class' : 'form-control'}),
'price' : forms.TextInput(attrs = {'class' : 'form-control'}),
'quantity' : forms.TextInput(attrs = {'class' : 'form-control'}),
}
buy_stock.html
{% extends 'base.html' %}
{% block content %}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Project-4 Buy Stock page</title>
</head>
<body>
<div class="container"></div>
<h1>Buy Stock</h1>
<br/>
<div>
<form action = "{%url 'main' %}" class="form-inline my-2 my-lg-0" method = "POST">
{% csrf_token %}
<input class="form-control mr-sm-2" type="search" placeholder="Get Stock Quote" aria-label="Search" name = "ticker">
<button class="btn btn-outline-secondary my-2 my-sm-0" type="submit">Stock Quote</button>
</form>
</div>
<br/>
<br/>
<div>
{% if ticker %}
{{ ticker }}
{% endif %}
{% if api %}
{% if api == "Error..." %}
There was a problem with your ticker symbol,
please try again...
{% else %}
<h2>{{ api.companyName }}</h2>
<br/>
{% load humanize %}
Price: ${{ api.latestPrice|intcomma }}<br/>
Previous Close: ${{ api.previousClose|intcomma }}<br/>
Market Cap: ${{ api.marketCap|intcomma }}<br/>
YTD Change: {{ api.ytdChange }}<br/>
52 Week High: ${{ api.week52High|intcomma }}<br/>
52 Week Low: ${{ api.week52Low|intcomma }}<br/>
<br/>
{% endif %}
{% else %}
{% endif %}
</div>
<br/>
<div>
<h6 class = "bold">Total in Account</h6>
{% for item in account %}
{% load humanize %}
<h1 class = "bold">${{ item.account|intcomma }}</h1>
{% endfor %}
</div>
<div class="container">
<div class="row">
<div class="col-sm">
<div class = "form-group">
<form action = "{%url 'buy_stock' %}" method = "POST">
{% csrf_token %}
{{ form.as_p }}
<div class="dropdown">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Trading Options
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenu2">
<button class="dropdown-item" type="sumbit">Buy</button>
<button class="dropdown-item" type="submit">Sell</button>
</div>
</div>
</form>
</div>
</div>
<div class="row">
<div class="col-sm">
<table class="table table-striped table-bordered table-hover">
<thead class="thead-dark">
<tr>
<th scope="col">Date Purchased</th>
<th scope="col">Company Name</th>
<th scope="col">Stock Price</th>
<th scope="col">Shares Purchased</th>
</tr>
</thead>
<tbody>
{% for item in stocks %}
<tr>
{% load humanize %}
<th scope="row">{{ item.created }}</th>
<td>{{ item.name }}</td>
<td>${{ item.price|intcomma }}</td>
<td>{{ item.quantity|intcomma }} Shares</td>
</tr>
{% endfor %}
</tbody>
</table>
<br/>
{% for item in stocks %}
<a class="btn btn-danger" href = "{% url 'sell_stock' item.id %}">Delete {{ item.name }}</a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<br/>
<br/>
<br/>
</body>
{% endblock %}

I think you should learn first about model relations. You data model will not work this way and fixing that, the rest will become more clear, hopefully.
An Order (you call it BuyStockModel) is the marriage of a product with a customer. So this order model should have links with the product (Stock) and the Customer (in your case represented by their Bank account). A broker has 2-way orders: both sales and purchase orders, but for the basic data model it's not important.
I'm going to use Order below, because BuyStockModel is not a good name for me :).
So you need two relations:
Order
/ \
Stock Bank
Django uses ForeignKeys for this relation:
class Order(models.Model):
stock = models.ForeignKey(Stock, on_delete=models.PROTECT)
account = models.ForeignKey(Bank, on_delete=models.PROTECT)
option = models.CharField(max_length = 10, default = 'buy')
price = models.DecimalField(
max_digits = 15, decimal_places = 2, default = 0
)
quantity = models.DecimalField(
max_digits = 15, decimal_places = 0, default = 0
)
created = models.DateTimeField(auto_now_add = True)
#property
def total_value(self):
return self.price * self.quantity
def __str__(self):
return (
f"{self.option.upper()}: {self.id} / qt: {self.quantity}"
f" / price: {self.price}"
)
You can now construct a single "OrderForm" using the Order model, that can render the accounts and stock tickers. That should get you on your way.

Related

How to filter my data to the active user in Django?

I'm trying to pull through only the latest data for the active user to my Django template. However, I'm currently receiving the below error.
Field 'id' expected a number but got <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x7f0682901a90>.
Models.py:
class HealthStats(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateField(auto_now=True)
weight = models.DecimalField(max_digits=5, decimal_places=2)
run_distance = models.IntegerField(default=5)
run_time = models.TimeField()
class Meta:
db_table = 'health_stats'
ordering = ['-date']
def __str__(self):
return f"{self.user} | {self.date}"
Views.py:
def health_hub(request):
model = HealthStats
def get_queryset(self):
user = self.user
latest = HealthStats.objects.filter(user=user).latest('date')
return latest
context = {
"user": model.user,
"weight": model.weight,
"date": model.date,
"run_distance": model.run_distance,
"run_time": model.run_time,
"stats": HealthStats,
"latest": get_queryset(model)
}
return render(request, 'health_hub.html', context)
health_hub.html:
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 text-center">
<h1>My Health Summary</h1>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row justify-content-center">
<h3>Welcome {{ latest.user }}!</h3>
<p>Please see below for your latest stats:</p>
<table class="table table-bordered">
<tr>
<td>Weight: {{ latest.weight }}</td>
<td>Run Distance: {{ latest.run_distance }} km</td>
</tr>
<tr>
<td>Run Time: {{ latest.run_time }}</td>
<td>Last Updated: {{ latest.date }}</td>
</tr>
</table>
According to the error message, it looks like there is an issue with what I'm trying to pull through, but I'm not sure where because I haven't asked for an ID? I'm just trying to filter the data in my models to show the current user's data, sorted by newest first.
It is your real code ? You mix CBV and FBV which is weird... Your variable model contains a class, not an instance. I supposed your context stats is bad too, but i do not find what you want to get with this var ?
Try this code for your view:
def health_hub(request):
latest = HealthStats.objects.filter(user=request.user).latest('date')
context = {
"user": latest.user,
"weight": latest.weight,
"date": latest.date,
"run_distance": latest.run_distance,
"run_time": latest.run_time,
"stats": HealthStats,
"latest": latest
}
return render(request, 'health_hub.html', context)
Some help on CBV with Django: https://docs.djangoproject.com/fr/3.2/topics/class-based-views/generic-display/

How can I show rating in product comments

My models:
class Comment(models.Model):
product = models.ForeignKey(Product ,on_delete=models.CASCADE, related_name='comments')
user = models.ForeignKey(User ,on_delete=models.CASCADE, max_length=80, related_name='comments_user')
body = models.TextField()
created_on = jmodels.jDateField(auto_now_add=True)
created_on_time = models.TimeField(auto_now_add=True,null=True)
active = models.BooleanField(default=False)
class Meta:
ordering = ['created_on']
def __str__(self):
return 'Comment {} by {}'.format(self.body, self.user)
class Rating(models.Model):
product = models.ForeignKey(Product ,on_delete=models.CASCADE)
user = models.ForeignKey(User ,on_delete=models.CASCADE)
score = models.IntegerField(default=0,
validators=[
MaxValueValidator(5),
MinValueValidator(0),
]
)
def __str__(self):
return 'rate {} by {} for {}'.format(self.score, self.user, self.product)
In product single page, I have comments part that I want show user rating if that user put comment in next of username and comment date.
My views :
def product_details(request, category_url, subcategory_url, product_url):
product = get_object_or_404(Product, product_url=product_url)
stocks = Stock.objects.filter(product=product)
rate = Rating.objects.filter(product=product, user=request.user)
all_rates = Rating.objects.filter(product=product)
all_rate_count = Rating.objects.filter(product=product).count()
all_rate = sum([all_rate.score for all_rate in all_rates])
all_rate = all_rate/all_rate_count
all_rate = all_rate*100/5
comments = product.comments.filter(product=product, active=True)
if request.method == "POST":
body = request.POST['body']
new_comment = Comment(user=request.user,product=product, body=body)
new_comment.save()
message_good = "نظر شما با موفقیت ثبت شد بعد از برسی نمایش داده میشود!"
ctx = {'product':product, 'stocks':stocks, 'rate':rate, 'all_rate':all_rate,
'comments':comments,
'message_good':message_good,
'all_rate_count':all_rate_count}
return render(request, 'products/product_details.html', ctx)
ctx = {'product':product, 'stocks':stocks, 'rate':rate, 'all_rate':all_rate,
'comments':comments,
'all_rate_count':all_rate_count}
return render(request, 'products/product_details.html', ctx)
And my html :
{% for comment in comments %}
<div class="comments" style="padding: 10px;">
<p class="font-weight-bold">
{{ comment.user }}
<span class=" text-muted font-weight-normal">
{{ comment.created_on }}
</span>
<span class=" text-muted font-weight-normal">
{{ comment.created_on_time|date:"G:i" }}
</span>
</p>
{{ comment.body | linebreaks }}
</div>
{% endfor %}
I updated my codes, and showed my views and my single html
so if please can help me about showing product rate by user for each comment that filtered by user.
or any better suggestion for other ways about rating or showing comments for single product page. thanks for helping
I would add a unique_together constraint to enforce that a user can only leave a single rating for a product.
class Rating(models.Model):
product = models.ForeignKey(Product ,on_delete=models.CASCADE)
user = models.ForeignKey(User ,on_delete=models.CASCADE)
score = models.IntegerField(default=0,
validators=[
MaxValueValidator(5),
MinValueValidator(0),
]
)
class Meta:
unique_together = ('product', 'user')
def __str__(self):
return 'rate {} by {} for {}'.format(self.score, self.user, self.product)
In your views, since you will only have one rating for a product per user.
rate = Rating.objects.get(product=product, user=request.user)
In your templates:
{% for comment in comments %}
<div class="comments" style="padding: 10px;">
<p class="font-weight-bold">
{{ comment.user }}
<span class=" text-muted font-weight-normal">
{{ comment.created_on }}
</span>
<span class=" text-muted font-weight-normal">
{{ comment.created_on_time|date:"G:i" }}
</span>
</p>
{{ comment.body | linebreaks }}
{{ rate.score }}
</div>
{% endfor %}

Filter - How to implement

I need to implement multi-choice filter in my Django software.
I have my model (database):
models.py
class Autor(models.Model):
naziv = models.CharField(max_length=30, null=False, blank=True)
email = models.EmailField(max_length=75, null=True, blank=True)
def __str__(self):
return str(self.naziv)
class Clanak(models.Model):
naslov = models.CharField(null=False, blank=True, max_length=120)
datumObjave = models.DateField(null=False, blank=False)
autor = models.ForeignKey(Autor, on_delete=models.CASCADE, null=True)
def __str__(self):
return str(self.naslov) + ', ' + str(self.datumObjave) + ', ' + str(self.autor)
My urls.py:
urlpatterns = [
path('filtar/',views.filtar, name='filtar'),
]
Views.py:
def filtar(request):
form = ChoiceForm(request.GET or None)
data = Clanak.objects.all()
if form.is_valid():
if 'Choice 1' in form.cleaned_data['filter']:
data = data.filter(naslov='name')
if 'Choice 2' in form.cleaned_data['year']:
data = data.filter(datumObjave__year='year')
return render(request, 'filtar.html', {'data': data, 'form': form})
Filtar.html:
<!DOCTYPE html>
<html>
<head>
{% extends 'base.html' %}
{% block main_content %}
<title></title>
</head>
<body>
<table border="1">
<tr>
<th>Naslov</th>
<th>Datum</th>
<th>Autor</th>
</tr>
{% for x in data %}
<tr>
<td>{{x.naslov}}</td>
<td>{{x.datumObjave}}</td>
<td>{{x.autor}}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
{% endblock %}
footer.html
<br>
<div>Filter: </div>
<form>
{% csrf_token %}
<fieldset>
<legend>Filtar - Thing that is being chosen</legend>
{{ form.as_p }}
<input type="submit" value="Submit">
</fieldset>
</form>
<br>
<div>Copyright by </div>
Forms.py
class ChoiceForm(forms.Form):
filter = forms.MultipleChoiceField(choices=(('Choice 1', 'Choice 1'), ('Choice 2', 'Choice 2')), widget=forms.CheckboxSelectMultiple)
name = forms.CharField(label='name')
year = forms.CharField(label='year')
Screenshot:
Now my question is:
My problem is that whatever I write in textfields it do nothing, just refresh page with all same data.
Name of dynamically created textboxes should be "nameinput" and "yearinput"
Your form is going to send GET request to the same page. So when view start processing, it fetch both choice values (if they exists), get all Clanak objects and filter them based on selected choices.
This way if no filters selected, you will get all objects. If both filters selected, it will filter both on names and year.
def filtar(request):
choice1 = request.GET.get('Autor', None)
choice2 = request.GET.get('Datum', None)
data = Clanak.objects.all()
if choice1:
data = data.filter(naslov='NAME')
if choice2:
data = data.filter(datumObjave__year=2019)
return render(request, 'filtar.html', {'data': data})
Note that it will not actually execute query before you try to access it (i.e. not until you render html).
Additionally, I would suggest you switch this form to django form, like this:
forms.py:
class ChoiceForm(forms.Form):
filter = forms.MultipleChoiceField(choices=(('Choice 1', 'Choice 1'), ('Choice 2', 'Choice 2')), widget=forms.CheckboxSelectMultiple)
views.py:
def filtar(request):
form = ChoiceForm(request.GET or None)
data = Clanak.objects.all()
if form.is_valid():
if 'Choice 1' in form.cleaned_data['filter']:
data = data.filter(naslov='NAME')
if 'Choice 2' in form.cleaned_data['filter']:
data = data.filter(datumObjave__year=2019)
return render(request, 'filtar.html', {'data': data, 'form': form})
footer.html:
<br>
<div>Filter: </div>
<form>
{% csrf_token %}
<fieldset>
<legend>Filtar - Thing that is being chosen</legend>
{{ form.as_p }}
<input type="submit" value="Submit">
</fieldset>
</form>
<br>
<div>Copyright by </div>

Invite Registered Users to vote in voting app

I'm trying to expand the Django tutorial for a school project and make it into a more usable voting app.
What I want is to allow users to create Polls and invite other registered users by email to vote on their Poll. Only the invited users will be allowed to vote.
My models.py:
class Poll(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published', auto_now_add=True)
is_active = models.BooleanField(default=True)
activation_date = models.DateTimeField('Activation Date', blank=True, null=True)
expiration_date = models.DateTimeField('Expiration Date', blank=True, null=True)
public_key = models.CharField(max_length=30, blank=True)
hash = models.CharField(max_length=128, blank=True)
timestamp = models.DateTimeField(auto_now=True)
def __str__(self):
return self.question_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'
class Choice(models.Model):
question = models.ForeignKey(Poll, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
class EligibleVoters(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
email = models.EmailField(null=True)
I have a Poll table which contains the Poll Title and other information regarding the Poll. I also created a separate Choices table (like in the tutorial) which has a ForeignKey to the Poll table and contains the poll choices.
I figured that in order to invite users as eligible voters I needed a third table with ForeignKeys to the Poll and User tables. So i created one.
I should note that I'm using the build-in user model.
Here's my views.py:
class NewPoll(CreateView):
model = Poll
fields = ['question_text', 'is_active', 'activation_date', 'expiration_date']
success_url = reverse_lazy('voting:index')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data['choices'] = ChoiceFormSet(self.request.POST)
# data['eligible_voters_poll'] = EligibleVotersFormSetPoll(self.request.POST)
# data['eligible_voters_user'] = EligibleVotersFormSetUser(self.request.POST)
#data['eligible_voters'] = EligibleVotersFormSet(self.request.POST)
else:
data['choices'] = ChoiceFormSet()
# data['eligible_voters_poll'] = EligibleVotersFormSetPoll()
# data['eligible_voters_user'] = EligibleVotersFormSetUser()
# data['eligible_voters'] = EligibleVotersFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
choices = context['choices']
# eligible_voters_poll = context['eligible_voters_poll']
# eligible_voters_user = context['eligible_voters_user']
#eligible_voters = context['eligible_voters']
with transaction.atomic():
self.object = form.save()
if choices.is_valid():
choices.instance = self.object
choices.save()
# if eligible_voters_poll.is_valid() and eligible_voters_user.is_valid():
# eligible_voters_poll.instance = self.object
# eligible_voters_poll.save()
# eligible_voters_user.instance = self.object
# eligible_voters_user.save()
#if eligible_voters.is_valid():
# eligible_voters.instance = self.object
# eligible_voters.save()
return super().form_valid(form)
I have commented the lines that are previous attempt into making it work. Without the commented lines the user is able to create a poll and also create choices. I'm having trouble making the invite part working though.
Here is my forms.py:
class PollForm(ModelForm):
activation_date = forms.DateTimeField(required=False)
expiration_date = forms.DateTimeField(required=False)
class Meta:
model = Poll
fields = ['question_text', 'is_active', 'activation_date', 'expiration_date']
class ChoiceForm(ModelForm):
class Meta:
model = Choice
exclude = ['votes']
ChoiceFormSet = inlineformset_factory(Poll, Choice, form=ChoiceForm, extra=1)
def form_maker(parent2):
class EligibleVotersForm(ModelForm):
# def __init__(self, user, poll, *args, **kwargs):
# self.user = user
# self.poll = poll
# super().__init__(*args, **kwargs)
def save(self, commit=True):
instance = super(EligibleVotersForm, self).save(commit=False)
# instance.parent1 = parent1
instance.parent2 = parent2
if commit:
instance.save()
return instance
class Meta:
model = EligibleVoters
fields = ['email']
return EligibleVotersForm
# EligibleVotersFormSetPoll = inlineformset_factory(Poll, EligibleVoters, form=EligibleVotersForm, extra=1)
# EligibleVotersFormSetUser = inlineformset_factory(User, EligibleVoters, form=EligibleVotersForm, extra=1)
# EligibleVotersFormSet = inlineformset_factory(Poll, EligibleVoters, form=form_maker(User), extra=1)
Here I have commented out again the lines that belong to my failed attempts at making it work. The closer I got it to working is to be able to invite by any email (not just the registered ones, which is what I want) and fill the EligibleVoters table with the emails associated with the poll_id. The user_id column though remained, empty.
Here's my html file:
{% extends 'base.html' %}
{% block content %}
{% load static %}
<h2>Poll Creation</h2>
<div class="col-md-4">
<form action='' method="post">
{% csrf_token %}
{{ form.as_p }}
<table class="table">
{{ choices.management_form }}
{% for form in choices.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle 'row1' 'row2' %} formset_row1">
{% for field in form.visible_fields %}
<td>
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<table class="table">
{{ eligible_voters.management_form }}
{% for form in eligible_voters.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle 'row1' 'row2' %} formset_row2">
{% for field in form.visible_fields %}
<td>
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Save"/> back to the list
</form>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="{% static 'voting/js/jquery.formset.js' %}"></script>
<script type="text/javascript">
$('.formset_row1').formset({
addText: 'add poll choice',
deleteText: 'remove',
prefix: 'choice_set'
});
$('.formset_row2').formset({
addText: 'add eligible voter',
deleteText: 'remove',
prefix: 'eligiblevoters_set'
});
</script>
{% endblock %}
Any ideas on how to make it work and only allow he eligible voters to vote for the polls they are invited to?
I was thinking that maybe my EligibleVoters model is wrong somehow and I need a ManyToMany field somewhere??

Django inlineformset will not submit

My application has two models, Recipe and RecipeIngredient, related by foreign key. Each Recipe may have many different RecipeIngredients (and different number of associated ingredients for each recipe). So using a dynamic formset to assign ingredients to a recipe seems to be the best approach.
I have a simple form that has a user enter a quantity and select a unit and ingredient from a dropdown. As a standalone form it works fine.
Unfortunately, I cannot get any formset incorporating it to submit.
I have searched many of the different formset Q&As here on StackOverflow but cannot see where the problem is. When I click 'submit', the form blinks and refreshes in place, with the entered values still in the form. No objects are created. No errors report to the console nor to the browser. The related code:
The models:
class RecipeBase(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
name = models.CharField(max_length=128,null=False)
creation_date = models.DateField("Record creation date",auto_now_add=True)
creation_user = models.CharField("Record creation user",max_length=150)
lastupdated_date = models.DateField(auto_now=True)
lastupdated_user = models.CharField(max_length=150)
category = models.ForeignKey(RecipeCategory,on_delete=models.CASCADE,related_name='recipes')
subcategory = models.ForeignKey(RecipeSubcategory,on_delete=models.CASCADE,related_name='recipes')
def __str__(self):
return str(self.name)
class RecipeIngredient(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
quantity = models.DecimalField(max_digits=8,decimal_places=3,null=False)
referenced_unit = models.ForeignKey(UnitDetail,on_delete=models.CASCADE)
referenced_ingredient = models.ForeignKey(Ingredient,on_delete=models.CASCADE)
parent_recipe = models.ForeignKey(RecipeBase,on_delete=models.CASCADE,related_name='ingredients')
def __str__(self):
return str(self.id)[:8]
The call to enter ingredients:
<a class='btn btn-success' href="{% url 'recipes:addingredient' pk=recipe_details.pk %}">Add Ingredients</a>
Calls the view:
class AddIngredientView(CreateView):
form_class = AddIngredientForm
model = RecipeIngredient
template_name = 'addingredientmultiple2.html'
success_url = "recipes:listrecipes"
def get_context_data(self, **kwargs):
parent_recipe_id = RecipeBase.objects.get(id=self.kwargs['pk'])
data = super(AddIngredientView, self).get_context_data(**kwargs)
if self.request.POST:
data['ingredients'] = AddIngredientFormset(self.request.POST)
data['parent_recipe_id'] = parent_recipe_id
else:
data['ingredients'] = AddIngredientFormset()
data['fixture'] = parent_recipe_id
return data
def form_valid(self, form, **kwargs):
user = self.request.user
fixture = RecipeBase.objects.get(id=self.kwargs['pk'])
context = self.get_context_data()
formset = AddIngredientFormset(self.request.POST)
if formset.is_valid():
ingredients = formset.save()
for recipeingredient in ingredients:
#recipeingredient.creation_user = str(request.user)
#recipeingredient.lastupdated_user = str(request.user)
recipeingredient.parent_recipe_id = parent_recipe_id
#recipeingredient.user = user
recipeingredient.save()
return super(AddIngredientView, self).form_valid(form)
Using form and formset:
class AddIngredientForm(forms.ModelForm):
quantity = forms.DecimalField(widget=forms.NumberInput(attrs={'data-placeholder': 0.00,'size': '8','label_tag': ''}))
referenced_unit = forms.ModelChoiceField(queryset=UnitDetail.objects.all())
referenced_ingredient = forms.ModelChoiceField(queryset=Ingredient.objects.all())
class Meta():
model = RecipeIngredient
fields = ('quantity','referenced_unit','referenced_ingredient',)
AddIngredientFormset = inlineformset_factory(RecipeBase,RecipeIngredient,form=AddIngredientForm,extra=1,can_delete=True)
And template:
{{ ingredients.media.css }}
<div class="container">
<h2>Add ingredients to {{ referring_recipe_name }}</h2>
<h4>{{ referring_recipe_id }}</h4>
</div>
<div class="col-md-4">
<form class="form-horizontal" enctype="multipart/form-data" method="post">{% csrf_token %}
<table class="table">
{{ ingredients.management_form }}
{% for form in ingredients.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input class='btn btn-success' type="submit" value="Save"/> back to the list
</form>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="{% static 'formset/jquery.formset.js' %}"></script>
<script type="text/javascript">
$('.formset_row').formset({
addText: 'ADD INGREDIENT',
deleteText: 'REMOVE',
prefix: 'ingredients'
});
</script>
{{ ingredients.media.js }}