A user have logged in and submitted 2 projects. I need to have a list of submitted projects by this user.
In my models.py I have a Project table:
class Project(db.Model):
__tablename__ = 'project'
users = db.relationship(User)
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) # means users.id is a ForeignKey for user_id
date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
title = db.Column(db.String(140), nullable=False) # Project TITLE
text = db.Column(db.Text, nullable=False) # JUST A TEXT OF THE PROJECT
def __init__ (self,title,text,user_id):
self.title = title
self.text = text
self.user_id = user_id
def __repr__(self):
return f"Project ID: {self.id} --Date: {self.date} -- {self.title}"
This is the route that I use to submit a project:
#app.route('/create_project', methods = ['GET','POST'])
#login_required
def create_project():
form = ProjectForm()
if form.validate_on_submit():
print(current_user)
project = Project(title = form.title.data,
text = form.text.data,
user_id = current_user.id
)
db.session.add(project)
db.session.commit
flash('Project created')
return redirect(url_for('welcome_user'))
return render_template('create_project.html', form=form)
When I submit a project, the code works well and I have a flash message that project is submitted. This means the data enters into a database.
However when I want to see the projects created by the same user:
# User's list of PROJECTS:
#app.route("/<first_name>")
#login_required # to make sure that user must be logged in to see that view
def user_posts(first_name):
#requesting a page
page = request.args.get('page',1,type=int)
user = User.query.filter_by(first_name=first_name).first_or_404()
# filtering the blog posts by username
projects = Project.query.filter_by(author=user).order_by(Project.date.desc()).paginate(page=page, per_page=5) # comes from backref='author' on models.py
return render_template('user_projects.html', projects=projects, user = user)
in user_projects.html I have the following:
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="jumbotron">
<div align="center">
<h1>Welcome to the page for {{user.first_name}}</h1>
</div>
</div>
USER PROJECTS <br>
project ID
<hr>
{{projects}}
<hr>
USER
<hr>
{{user}}
<hr>
{% for post in projects.items %}
<h2> {{post.title}} </h2>
<p class="text-muted">Published On: {{post.date.strftime("%Y-%m-%d")}}</p>
<br>
<p>{{post.text}}</p>
<br>
{% endfor %}
</div>
{% endblock %}
While I do have a user information inside this template, I can access name, surname etc., I have nothing inside a projects object, however I have <flask_sqlalchemy.Pagination object at 0x0000024F5E334CD0> on a screen - meaning that at least the query worked well and there is something in it.
I wonder how can I verify the content of the given table - when I create a test route:
#app.route('/test')
def test():
test = Project.query.all()
return render_template('test.html', test=test)
I see nothing inside a test object. Why?
I think you forgot the brackets behind the commit command within your route to save. The function is therefore not called and the new project is not stored in the database. The table is empty.
#app.route('/create_project', methods = ['GET','POST'])
#login_required
def create_project():
form = ProjectForm()
if form.validate_on_submit():
project = Project(
title = form.title.data,
text = form.text.data,
user_id = current_user.id
)
db.session.add(project)
db.session.commit() # <-- The brackets were missing here!
flash('Project created')
return redirect(url_for('welcome_user'))
return render_template('create_project.html', form=form)
Your queries appear correct.
Related
I have a website I am trying to build for personal use, and it possesses two id's one for a meeting (where the race is run) and one for the event (the race number). The event id is in the form of "123456_01" and is passed in the model as a primary key for the Event model, as seen below...
class Event(models.Model):
meeting = models.CharField(max_length=500)
meetingID = models.ForeignKey(Meeting, on_delete='CASCADE', related_name='races')
eventID = models.CharField(max_length=300, primary_key=True)
venue = models.CharField(max_length=600, null=True)
race_no = models.CharField(max_length=2)
event_time = models.TimeField()
status = models.CharField(max_length=100)
distance = models.CharField(max_length=600)
I currently have the views file set up as follows:
class EventDetailView(DetailView,LoginRequiredMixin):
context_object_name = 'race_detail'
template_name = 'event.html'
model = models.Event
slug_url_kwarg = 'eventID'
I also have my front end set up so that at present when I click on a certain race, it automatically navigates to the page with the link http://127.0.0.1:8000/app/123456_01/, so that part is working through this config in the HTML:
{% url 'bettingUI:race' eventID=events.eventID %}
the problem I seem to be having is with the configuration of the urls.py file and possibly something I am missing in the views.py file.
my urls.py file is set up as follows :
from django.urls import path, include
from . import views
app_name = 'bettingUI'
urlpatterns = [
path('',views.DashListView.as_view(),name='dashboard'),
path('<eventID>/', views.EventDetailView.as_view(), name='race'),
]
I thought from reading the docs that I need to use a slug because of the '_' character in the ID I am passing in but I am constantly getting an error in the browser stating that it can not resolve keyword 'slug' into the field. Choices are: dro_eventID, dro_meetingID, dro_meetingID_id, event_time, meeting, race_no, runners, status, venue ( **the fields of the model). If I change the urls.py file to the below, I get the same error:
path('<slug:eventID>/', views.EventDetailView.as_view(), name='race'),
I am a bit lost here so would love some guidance.
Thank you.
I worked it out, the answer is to input <slug:pk>
but now I am getting an error at my dashpage (the page i land at to click through to the race page):
NoReverseMatch at /app/
Reverse for 'race' with keyword arguments '{'eventID': '1216859_01'}' not found. 1 pattern(s) tried: ['app/(?P<pk>[-a-zA-Z0-9_]+)/$']
So I give it again now the working version:
First you should add a slug field to your Event Model and this will let you use slug, so your model will look like this:
from django.utils.text import slugify
class Event(models.Model):
meeting = models.CharField(max_length=500)
meetingID = models.ForeignKey(Meeting, on_delete='CASCADE', related_name='races')
eventID = models.CharField(max_length=300, primary_key=True)
venue = models.CharField(max_length=600, null=True)
race_no = models.CharField(max_length=2)
event_time = models.TimeField(null=True)
status = models.CharField(max_length=100, null=True)
distance = models.CharField(max_length=600, null=True)
slug = models.SlugField(max_length=50, null=True)
def save(self, *args, **kwargs):
self.slug = slugify(self.eventID, allow_unicode=True)
return super(Event, self).save(*args, **kwargs)
Notice the save() function and in that we added a slugify() method to slugify the eventID field at event savings.
Then your views should look like these:
from .models import Event, Meeting
class EventList(ListView):
model = Event
template_name = 'event_list.html'
context_object_name = 'race_list'
class EventDetailView(DetailView,LoginRequiredMixin):
context_object_name = 'race_detail'
template_name = 'myusers1/event.html' # this could be only event.html if the template is in yourapp/templates/ folder directly
model = Event
slug_url_kwarg = 'slug'
Notice in the above view that we now use actually the default slug definition.
I put the listview url under races/ sub-url but you can put it anywhere you want. And in your urls.py you can now use the slug values correctly like:
path('races/<slug:slug>/', views.EventDetailView.as_view(), name='race'),
path('races/', views.EventList.as_view(), name='race_list'),
In my trial app the templates look like the followings: listview template:
{% extends 'myusers1/base.html' %}
{% block content %}
<div class"container">
<div class="col col-lg-2">
<h2>Races</h2>
<ul>
{% for race in race_list %}
<div class="col-xs-12 .col-md-8"><li> {{ race.venue }} </li></div>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}
And the detail template looks like this:
{% extends 'myusers1/base.html' %}
{% block content %}
<div class"container">
<div class="col col-lg-2">
<h2>Race Details</h2>
<div class="col-xs-12 .col-md-8"> <h4>Venue name: </h4> {{ race_detail.venue}} </div>
<div class="col-xs-12 .col-md-8"> <h4>Event ID: </h4> {{ race_detail.eventID }} </div>
<div class="col-xs-12 .col-md-8"> <h4>Meeting name: </h4> {{ race_detail.meeting }} </div>
<div class="col-xs-12 .col-md-8"> <h4>Meeting ID: </h4> {{ race_detail.meetingID.id }} </div>
</div>
</div>
{% endblock %}
And the visual result about how dynamic urls work using the above:
I hope that the above will help you to finalize your app list and details view now. Cheers.
I think I found a solution here try this:
url.py:
path('<slug:eventID>/', views.EventDetailView.as_view(), name='race')
Now you can simple get the instance of Event in your EventDetailView generic view by using get_object method like this:
class EventDetailView(DetailView, LoginRequiredMixin):
context_object_name = 'race_detail'
template_name = 'event.html'
model = models.Event
def get_object(self):
e1 = Event.objects.get(eventID=self.kwargs['eventID'])
print (e1.eventID) # or e1.pk gives: 123456_01
return e1
You can also change your eventID from CharField to SlugField. And still have it working.
Let's consider the following models
models.py
Class Brand(models.Model):
company_name = models.CharField(max_length=100)
class CarModel(models.Model):
brand = models.ForeignKey(Brand)
name = models.CharField(max_length=100)
Class FleetCars(models.Model):
model_car = models.Foreignkey(CarModel)
What is the best way to solve this problem in django?
Suppose a form (for insertions in FleetCars) consists of two select elements, like this:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<br />Brand:
<select>
<option value="Brand1">Brand1</option>
<option value="Brand2">Brand2</option>
</select>
<br />
<br />Model:
<select>
<option value="Model1_B1">Model1_B1</option>
<option value="Model1_B2">Model1_B2</option>
</select>
</body>
</html>
In this case, I want the options in the second select to depend on the value selected in the first. For example, if the user chose Brand1 for a Brand in the first select, the second select would be filtered with only cars whose Brand was Brand1, that is, only "Model1_B1".
Obs.
I saw many solutions with forms.ModelChoiceField, but only works with edit and since the user do not change the brand.
After hours and hours of research, without success, I decided to try to solve on my own. The solution that I found maybe don't be the best or the more elegant, but is working. (For download full Django project, click on this repo => https://github.com/Sidon/djfkf/.)
models.py
from django.db import models
class Brand(models.Model):
company_name = models.CharField(max_length=100)
def __str__(self):
return self.company_name
class Car(models.Model):
brand = models.ForeignKey(Brand)
name = models.CharField(max_length=100)
def brand_name(self):
return self.brand.company_name
def __str__(self):
return self.name
class Fleet(models.Model):
car = models.ForeignKey(Car)
description = models.CharField(max_length=100)
def car_name(self):
return self.car.name
def brand(self):
return self.car.brand.company_name
def __str__(self):
return self.description
The goal is to register cars on the fleet. The only fields that are will be recorded: Car (foreign key) and description. On the form, there will be one select element for brands that will work just only as a helper for to filter the car's combo box.
forms.py
import json
from django import forms
from .models import *
class RegCarForm(forms.ModelForm):
dcars = {}
list_cars = []
for car in Car.objects.all():
if car.brand.company_name in dcars:
dcars[car.brand.company_name].append(car.name)
else:
dcars[car.brand.company_name] = [car.name]
list_cars.append((car.name,car.name))
brands = [str(brand) for brand in Brand.objects.all()]
brand_select = forms.ChoiceField(choices=([(brand, brand) for brand in brands]))
car_select = forms.ChoiceField(choices=(list_cars))
brands = json.dumps(brands)
cars = json.dumps(dcars)
class Meta:
model = Fleet
fields = ('brand_select', 'car_select', 'description',)
RegCarForm is a form for register cars, there are three fields: brand_select, car_select, and description. In addition, I defined two JSON attributes: 1) a dictionary whose keys are brands (strings) and values are lists of respective's cars and 2) A list of strings that represent the brands. Those two attributes will work as helpers for JS functions.
views.py
from django.shortcuts import render
from .forms import RegCarForm
from .models import *
def regcar(request):
if request.method == 'POST':
car_form = RegCarForm(data=request.POST)
if car_form.is_valid():
cdata = car_form.cleaned_data.get
car_selected = Car.objects.filter(name=cdata('car_select'))
reg1 = Fleet(car_id=car_selected[0].id, description=cdata('description'))
reg1.save()
else:
print ('Invalid')
else:
car_form = RegCarForm()
return render(request, 'core/regcar.html', {'car_form': car_form})
The view is practically auto-explanatory. Assigns the Form to the car_form variable, render the template core/regcar.html and, after Post, make the validation of the form and save the data.
regcar.html (template django)
{% extends "base.html" %}
{% block head %}
{% endblock %}
{% block content %}
<h1>Registering cars on the fleet. <br />(Populate one drop down based on selection in another)</h1>
<p>Change the contents of drop down Car based on the selection in dropdown Brand, using Django-forms + Javascritp</p>
<div class="select-style">
<form action="." method="post">
{% csrf_token %}
{{ car_form.as_p }}
<p><input type="submit" value="Register a car"></p>
</form>
</div>
{% endblock %}
{% block js %}
{% include "js1.html" %}
{% endblock %}
The template only just renders the form and load the script JS. Nothing else.
Finally, the js script, that makes the hard work.
{% block js %}
<script language="javascript">
$('#id_brand_select').change(function() {populateCar(this)});
$('#id_description').addClass('descriptions');
cars = {{ car_form.cars | safe }}
brands = {{ car_form.brands | safe}};
populateBrand();
$("#id_car_select").empty();
$("#id_car_select").append('<option value="" disabled selected>First select a brand</option>');
function populateBrand() {
$('#id_brand_select').empty();
$("#id_brand_select").append('<option value="" disabled selected>Select your option</option>');
$.each(brands, function(v) {
$('#id_brand_select')
.append($("<option></option>")
.attr("value", brands[v])
.text(brands[v]));
});
}
function populateCar(event) {
brand = $("#id_brand_select option:selected").text();
$("#id_car_select").empty();
$("#id_car_select").append('<option value="" disabled selected>Select your option</option>');
for (let [b, bcars] of Object.entries(cars)) {
if (b == brand) {
//alert(b);
for (car in bcars) {
$('#id_car_select')
.append($("<option></option>")
.attr("value", bcars[car])
.text(bcars[car]));
}
}
}
}
</script>
{% endblock %}
When the document is loaded, this script assigns the change event of brand_select (combo for selection of brand) to the function poplulateCar, assign the form's JASON attributes (cars and brands) to a JS variables and call the populateBrand function.
Links:
Full project in Django:
https://github.com/Sidon/djfkf/
class Country(models.Model):
country_name=models.CharField(max_length=10, blank=True, null=True)
class State(models.Model):
state_name=models.CharField(max_length=10, blank=True, null=True)
class MyCustomModal(models.Model):
country = models.ForeignKey(Country, on_delete=models.CASCADE, null=True, blank=True)
state = models.ForeignKey(State, on_delete=models.CASCADE, null=True, blank=True)
Here is my Form
class MyCustomForm(forms.ModelForm):
class Meta:
model = MyCustomModal
fields = [
'country',
'state',
]
def __init__(self, *args, **kwargs):
super(MyCustomForm, self).__init__(*args, **kwargs)
self.fields['country'] = forms.ChoiceField(choices=[('1','india'),('2','US')])
self.fields['state'].queryset = State.objects.filter(pk=2)
I have seen a large number of tutorials that show login forms with flask and flash-wtf but none where multiple select boxes are populated from database table values.
This is what I am trying to do:
A simple registration form:
First name
Last name
Address Line 1
Address Line 2
City
State Id (populated from states library query of Id,state)
Country Id (populated from countries library query of country, id)
Sample code or a link to a walk through would be greatly appreciated.
I tried to find a explanation for how to do this and couldn't find one. So I'm going to write one here. This is how I do things, there's probably better ways to go about it.
Source Code
You can download the full source code for this tutorial on my github account. I'm pretty much copying and pasting from the source code, but just in case github dies some day here we go.
Configuration
Need to configure our application and the database connection. In most cases
you probably want to load all of this from a configuration file.
In this tutorial we're just going to use a basic sqlalchemy test database.
app = Flask(__name__)
app.config['SECRET_KEY'] = 'Insert_random_string_here'
Set this configuration to True if you want to see all of the SQL generated.
app.config['SQLALCHEMY_ECHO'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
WTForms configuration strings
app.config['WTF_CSRF_ENABLED'] = True
CSRF tokens are important. Read more about them here,
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
app.config['WTF_CSRF_SECRET_KEY'] = 'Insert_random_string_here'
db = SQLAlchemy(app)
SQLALchemy Model
Next we need to create our model classes that will be used during the creation
of the database and also when we want to manipulate the database. This should
normally be it's own seperate file.
I'm importanting from here as if this was it's own seperate file.
Normally you'd have to import doing something like
from application import db
class RegisteredUser(db.Model):
"""
loads and pushes registered user data after they have signed up.
SQLalchemy ORM table object which is used to load, and push, data from the
server memory scope to, and from, the database scope.
"""
__tablename__ = "RegisteredUser"
#all of the columns in the database.
registered_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(70))
last_name = db.Column(db.String(70))
address_line_one = db.Column(db.String(256))
address_line_two = db.Column(db.String(256))
city = db.Column(db.String(50))
"""
Now we're going to create all of the foreign keys for the RegisteredUser
table. The db.relationship section allows us to easily and automatically
join the other tables with registeredUser. The Join will only take place
if you attempt to access columns from the State or country table.
For more on Foreign keys using SQLAlchemy go to
"""
state_id = db.Column(
db.Integer,
db.ForeignKey('State.state_id'),
nullable=False)
#retrives the users name for display purposes.
state_by = db.relationship(
'State',
foreign_keys=[state_id],
backref=db.backref('State', lazy='dynamic'))
country_id = db.Column(
db.Integer,
db.ForeignKey('Country.country_id'),
nullable=False)
#retrives the users name for display purposes.
country_by = db.relationship(
'Country',
foreign_keys=[country_id],)
#this is the method and function style I've chosen when lines are too long
def __init__(
self,
first_name,
last_name,
address_line_one,
address_line_two,
city,
state_id,
country_id):
"""
Used to create a RegisteredUser object in the python server scope
We will be calling these init functions every time we use
RegisteredUser() as a 'function' call. It will create a SQLalchemy ORM
object for us.
"""
self.first_name = first_name
self.last_name = last_name
self.address_line_one = address_line_one
self.address_line_two = address_line_two
self.city = city
self.state_id = state_id
self.country_id = country_id
class State(db.Model): # pylint: disable-msg=R0903
"""
Holds State names for the database to load during the registration page.
SQLalchemy ORM table object which is used to load, and push, data from the
server memory scope to, and from, the database scope.
"""
__tablename__ = "State"
state_id = db.Column(db.Integer, primary_key=True)
state_name = db.Column(db.String(10), unique=True)
def __init__(self, state_name):
"""
Used to create a State object in the python server scope
"""
self.state_name = state_name
class Country(db.Model): # pylint: disable-msg=R0903
"""
Holds Country names for the database to load during the registration page.
SQLalchemy ORM table object which is used to load, and push, data from the
server memory scope to, and from, the database scope.
"""
__tablename__ = "Country"
country_id = db.Column(db.Integer, primary_key=True)
#longest country length is currently 163 letters
country_name = db.Column(db.String(256), unique=True)
def __init__(self, country_name):
"""
Used to create a Country object in the python server scope
"""
self.country_name = country_name
def create_example_data():
"""
Generates all of the demo data to be used later in the tutorial. This is
how we can use our ORM objects to push data to the database.
NOTE: create_example_data is called at the very bottom of the file.
"""
#Create a bunch of state models and add them to the current session.
#Note, this does not add rows to the database. We'll commit them later.
state_model = State(state_name="WA")
db.session.add(state_model)
state_model = State(state_name="AK")
db.session.add(state_model)
state_model = State(state_name="LA")
db.session.add(state_model)
#Normally I load this data from very large CVS or json files and run This
#sort of thing through a for loop.
country_model = Country("USA")
db.session.add(country_model)
country_model = Country("Some_Made_Up_Place")
db.session.add(country_model)
# Interesting Note: things will be commited in reverse order from when they
# were added.
try:
db.session.commit()
except IntegrityError as e:
print("attempted to push data to database. Not first run. continuing\
as normal")
WTForm
Now we're going to make our WTForms objects. These will have the data aquired
from the database placed on them, then we will pass them to our template files
where we will render them.
I'm importanting from here as if this was it's own seperate file.
import wtforms
import wtforms.validators as validators
from flask.ext.wtf import Form
class RegistrationForm(Form):
"""
This Form class contains all of the fileds that make up our registration
Form.
"""
#Get all of the text fields out of the way.
first_name_field = wtforms.TextField(
label="First Name",
validators=[validators.Length(max=70), validators.Required()])
last_name_field = wtforms.TextField(
label="Last Name",
validators=[validators.Length(max=70), validators.Required()])
address_line_one_field = wtforms.TextField(
label="Address",
validators=[validators.Length(max=256), validators.Required()])
address_line_two_field = wtforms.TextField(
label="Second Address",
validators=[validators.Length(max=256), ])
city_field = wtforms.TextField(
label="City",
validators=[validators.Length(max=50), validators.Required()])
# Now let's set all of our select fields.
state_select_field = wtforms.SelectField(label="State", coerce=int)
country_select_field = wtforms.SelectField(label="Country", coerce=int)
Views
import flask
def populate_form_choices(registration_form):
"""
Pulls choices from the database to populate our select fields.
"""
states = State.query.all()
countries = Country.query.all()
state_names = []
for state in states:
state_names.append(state.state_name)
#choices need to come in the form of a list comprised of enumerated lists
#example [('cpp', 'C++'), ('py', 'Python'), ('text', 'Plain Text')]
state_choices = list(enumerate(state_names))
country_names = []
for country in countries:
country_names.append(country.country_name)
country_choices = list(enumerate(country_names))
#now that we've built our choices, we need to set them.
registration_form.state_select_field.choices = state_choices
registration_form.country_select_field.choices = country_choices
#app.route('/', methods=['GET', 'POST'])
def demonstration():
"""
This will render a template that displays all of the form objects if it's
a Get request. If the use is attempting to Post then this view will push
the data to the database.
"""
#this parts a little hard to understand. flask-wtforms does an implicit
#call each time you create a form object. It attempts to see if there's a
#request.form object in this session and if there is it adds the data from
#the request to the form object.
registration_form = RegistrationForm()
#Before we attempt to validate our form data we have to set our select
#field choices. This is just something you need to do if you're going to
#use WTForms, even if it seems silly.
populate_form_choices(registration_form)
#This means that if we're not sending a post request then this if statement
#will always fail. So then we just move on to render the template normally.
if flask.request.method == 'POST' and registration_form.validate():
#If we're making a post request and we passed all the validators then
#create a registered user model and push that model to the database.
registered_user = RegisteredUser(
first_name=registration_form.data['first_name_field'],
last_name=registration_form.data['last_name_field'],
address_line_one=registration_form.data['address_line_one_field'],
address_line_two=registration_form.data['address_line_two_field'],
city=registration_form.data['city_field'],
state_id=registration_form.data['state_select_field'],
country_id=registration_form.data['country_select_field'],)
db.session.add(registered_user)
db.session.commit()
return flask.render_template(
template_name_or_list='success.html',
registration_form=registration_form,)
return flask.render_template(
template_name_or_list='registration.html',
registration_form=registration_form,)
runserver.py
Finally, this is for development purposes only. I normally have this in a
file called RunServer.py. For actually delivering your application you should
run behind a web server of some kind (Apache, Nginix, Heroku).
if __name__ == '__main__':
db.create_all()
create_example_data()
app.run(debug=True)
Templates
in macros.html
{% macro render_field(field) %}
<dt>{{ field.label }}
<dd>{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endmacro %}
{% macro render_data(field) %}
<dt>{{ field.label }}
<dd>{{ field.data|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endmacro %}
In registration.html
{% from "macros.html" import render_field %}
<form method=post action="/">
{{registration_form.hidden_tag()}}
<dl>
{{ render_field(registration_form.first_name_field) }}
{{ render_field(registration_form.last_name_field) }}
{{ render_field(registration_form.address_line_one_field) }}
{{ render_field(registration_form.address_line_two_field) }}
{{ render_field(registration_form.city_field) }}
{{ render_field(registration_form.state_select_field) }}
{{ render_field(registration_form.country_select_field) }}
</dl>
<p><input type=submit value=Register>
</form>
Finally, in success.html
{% from "macros.html" import render_data %}
<h1> This data was saved to the database! </h1>
<form method=post action="/">
{{registration_form.hidden_tag()}}
<dl>
{{ render_data(registration_form.first_name_field) }}
{{ render_data(registration_form.last_name_field) }}
{{ render_data(registration_form.address_line_one_field) }}
{{ render_data(registration_form.address_line_two_field) }}
{{ render_data(registration_form.city_field) }}
{{ render_data(registration_form.state_select_field) }}
{{ render_data(registration_form.country_select_field) }}
</dl>
<p><input type=submit value=Register>
</form>
I have the three models User (django.contrib.auth), Screening and User_Screening. The User_Screening is a m2m table with the extra field status.
#models.py
from django.db import models
from django.contrib.auth.models import User
class Screening(models.Model):
title = models.CharField(max_length=255)
start = models.DateTimeField()
user_relation = models.ManyToManyField(User, blank=True,
through='User_Status')
class User_Status(models.Model):
ATTENDING = 'c'
NOT_ATTENDING = 'n'
PROJECTION = 'p'
STATUS_CHOICES = (
(ATTENDING, 'attending'),
(NOT_ATTENDING, 'not attending'),
(PROJECTING, 'projecting'),
)
screening = models.ForeignKey(Screening)
user = models.ForeignKey(User)
status = models.CharField(max_length=1, choices=STATUS_CHOICES)
Now I want to make a view, which shows all upcoming screenings. So far, so easy:
#views.py
#login_required()
def index(request):
current_screenings = Screening.objects.filter(start__gte=timezone.now())
context = {'current_screenings': current_screenings}
return render(request, 'schedule/index.html', context)
In this view, logged in users should be able, to update their status (from the User_Screening table). It could also be, that the user does not yet have a record for this screening, so one should be created.
I don't understand, how I could archive a form dropdown field for each screening, where the user can select his status. (Either ? if no status is set yet, attending, not attending or projection)
From what I understand I need multiple forms, that are aware what screening they are related to.
Also, Formsets seem not to work, because I can't always fill a form with initial data, as there could be records missing for some or all screenings. Furthermore I would not know, which form belongs to which of the screening objects.
Update:
What I want to end up with in HTML is something like this:
<form>
<h1>Current Screening 1</h1>
<select onchange="submit()" name="screening_user" id="s1">
<option value="att">Attending</option>
<option value="not_att">Not Attending</option>
<option selected="selected" value="pro">Projection</option>
</select>
<h1>Current Screening 2</h1>
<select onchange="submit()" name="screening_user" id="s2">
<!-- The 'Please Select' option is only visible, if the user does not
have a relation in 'User_Screening' for this screening -->
<option selected="selected" value="none">Please Select</option>
<option value="att">Attending</option>
<option value="not_att">Not Attending</option>
<option value="pro">Projection</option>
</select>
<!-- More Screenings -->
<h1>Current Screening n</h1>
<!-- select for screening n -->
</form>
Therefore a changing amount of forms is needed, from the same form with preloaded data according to the logged in user.
If a screening has a m2m relation to Users, than the attending users can be in that list. If not in attending... Well, than they are not attending! Does that make sense?
class Screening(models.Model):
title = models.CharField(max_length=255)
date = models.DateTimeField()
attending = models.ManyToManyField(User)
Form:
class ScreeningForm(ModelForm):
class Meta:
model = Screening
fieds = ['attending', ]
Formset:
ScreeningFormSet = modelformset_factory(Screenig, max_num=1)
formset = ScreeningFormSet(Screening=Screening.objects.filter(date__gte=now))
On one hand you could send the form data via an ajax request. In that request you would simply send one form and process the data. You would not need any formsets. Depending on your usecase this may add unnecessary traffic to your server.
Another solution would be to add another STATUS_CHOICE like 'nothing selected' as default value for the form that is used if there is no entry for the Screening User combination in the db. In the POST handler of your view you you can then just check if the form data is set to this value. In this case you simply ignore the form. If it is another value, then you set the db entry accordingly.
With some help from #django on feenode, I solved my problem. In the end, I stuck with formsets.
Considering the models.py from my question I had to change User_Status slightly, adding a NO_STATUS choice for the Select-Widget if no relation yet exist for the screening. Note that NO_STATUS is not a choice for the model.CharField!
#models.py
class User_Status(models.Model):
NO_STATUS = '?'
PROJECTIONIST = 'p'
ATTENDING = 'c'
NOT_ATTENDING = 'n'
STATUS_CHOICES = [
(ATTENDING, 'Anwesend'),
(NOT_ATTENDING, 'Nicht anwesend'),
(PROJECTIONIST, 'Vorführer'),
]
STATUS_CHOICES_AND_EMPTY = [(NO_STATUS, 'Please choose')] + STATUS_CHOICES
screening = models.ForeignKey(Screening)
user = models.ForeignKey(User)
status = models.CharField(max_length=1, choices=STATUS_CHOICES,
default=ATTENDING)
Next up, the form. The modified __init__ takes care, that 'Please choose' is only a valid choice, if that is set as the initial value for status. Otherwise, the choice is just not displayed.
#forms.py
class ScreeningUserStatusForm(forms.Form):
screening_id = forms.IntegerField(min_value=1)
status = forms.ChoiceField(choices=User_Status.STATUS_CHOICES_AND_EMPTY,
widget=forms.Select(attrs={"onChange":'submit()'}))
def __init__(self, *args, **kwargs):
super(ScreeningUserStatusForm, self).__init__(*args, **kwargs)
if self['status'].value() != User_Status.NO_STATUS:
#Once, a status is selected, the status should not be unset.
self.fields['status'].choices=User_Status.STATUS_CHOICES
Finally the view, that uses a formset to put all current screenings in it.
def update_user_status(screening, user, status):
#Get old status, if already exists.
new_status = User_Status.objects.get_or_create(screening=screening,
user=user)
# Add to selected status
new_status.status = status
new_status.save()
#login_required()
def index(request):
"""
displays all upcoming screenings
"""
# Get current screenings
current_screening_set = Screening.objects.filter(start__gte=timezone.now() - datetime.timedelta(hours=24)).order_by('start')
current_screening_list = current_screening_set.values('id')
ScreeningFormSet = formset_factory(ScreeningUserStatusForm, extra=0)
if request.method == 'POST':
#Get a formset bound to data from POST
formset = ScreeningFormSet(request.POST, request.FILES)
if formset.is_valid():
for form in formset.cleaned_data:
s = get_object_or_404(Screening, pk=form['screening_id'])
if form['status'] != User_Status.NO_STATUS:
update_user_status(screening=s, user=request.user, status=form['status'])
else:
#create a fresh formset
for form_data in current_screening_list:
screening = Screening.objects.get(pk=form_data['id'])
status = User_Status.objects.filter(user=request.user, screening=screening)
if status.count() != 1:
form_data['status'] = u'?'
else:
form_data['status'] = status.first().status
form_data['screening_id'] = form_data['id']
formset = ScreeningFormSet(initial=current_screening_list)
forms_and_curr_screenings = zip(formset.forms, current_screening_set)
context = {'formset' : formset, 'current_screenings' : forms_and_curr_screenings}
return render(request, 'schedule/index.html', context)
The formset.forms are zipped together with the current_screening_set, to provide additional data to each from. formset is additionally given to the template for the management_form.
A template could look like this
<!-- index.html -->
{% if current_screenings %}
<form method="post">
{{ formset.management_form }}
{% csrf_token %}
<table>
<thead>
<tr>
<th>Screening</th>
<th>My Status</th>
</tr>
</thead>
<tbody>
{% for form, screening in current_screenings %}
<tr>
<td>{{ screening }}</a></td>
<td>
{{ form.screening_id.as_hidden }}
{{ form.status }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
{% endif %}
I'm new to Django and I'm creating an app to create and display employee data for my company.
Currently the model, new employee form, employee table display, login/logout, all works. I am working on editing the current listings.
I have hover on row links to pass the pk (employeeid) over the url and the form is populating correctly- except the manytomanyfields are not populating, and the pk is incrementing, resulting in a duplicate entry (other than any data changes made).
I will only put in sample of the code because the model/form has 35 total fields which makes for very long code the way i did the form fields manually (to achieve a prettier format).
#view.py #SEE EDIT BELOW FOR CORRECT METHOD
#login_required
def employee_details(request, empid): #empid passed through URL/link
obj_list = Employee.objects.all()
e = Employee.objects.filter(pk=int(empid)).values()[0]
form = EmployeeForm(e)
context_instance=RequestContext(request) #I seem to always need this for {%extend "base.html" %} to work correctly
return render_to_response('employee_create.html', locals(), context_instance,)
#URLconf
(r'^employee/(?P<empid>\d+)/$', employee_details),
# snippets of employee_create.html. The same template used for create and update/edit, may be a source of problems, they do have different views- just render to same template to stay DRY, but could add an additional layer of extend for differences needed between the new and edit requests EDIT: added a 3rd layer of templates to solve this "problem". not shown in code here- easy enough to add another child template
{% extends "base.html" %}
{% block title %}New Entry{% endblock %}
{% block content %}
<div id="employeeform">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<form action="/newemp/" method="post" class="employeeform">{% csrf_token %} #SEE EDIT
<div class="left_field">
{{ form.employeeid.value }}
{{ form.currentemployee.errors }}
<label for="currentemployee" >Current Employee?</label>
{{ form.currentemployee }}<br/><br/>
{{ form.employer.errors }}
<label for="employer" class="fixedwidth">Employer:</label>
{{ form.employer }}<br/>
{{ form.last_name.errors }}
<label for="last_name" class="fixedwidth">Last Name:</label>
{{ form.last_name }}<br/>
{{ form.facility.errors }} #ManyToMany
<label for="facility" class="fixedwidth">Facility:</label>
{{ form.facility }}<br/><br/>
</div>
<div id="submit"><br/>
<input type="submit" value="Submit">
</div>
</form>
#models.py
class Employee(models.Model):
employeeid = models.AutoField(primary_key=True, verbose_name='Employee ID #')
currentemployee = models.BooleanField(null=False, blank=True, verbose_name='Current Employee?')
employer = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
facility = models.ForeignKey(Facility, null=True, blank=True)
base.html just has a header on top, a menu on the left and a big empty div where the forms, employee tables, etc all extend into.
screenshot2
So, how do I need to change my view and/or the in the template to update an entry, rather than creating a new one? (
And how do I populate the correct foriegnkeys? (the drop down boxes have the right options available, but the "-----" is selected even though the original database entry contains the right information.
Let me know if i need to include some more files/code
I have more pics too but i cant link more or insert them as a new user :< I'll just have to contribute and help out other people! :D
EDIT:
I've been working on this more and haven't gotten too far. I still can't get the drop-down fields to select the values saved in the database (SQLite3).
But the main issue I'm trying to figure out is how to save as an update, rather than a new entry. save(force_update=True) is not working with the default ModelForm save parameters.
views.py
def employee_details(request, empid):
context_instance=RequestContext(request)
obj_list = Employee.objects.all()
if request.method == 'POST':
e = Employee.objects.get(pk=int(empid))
form = EmployeeForm(request.POST, instance=e)
if form.is_valid():
form.save()
return HttpResponseRedirect('/emp_submited/')
else:
e = Employee.objects.get(pk=int(empid))
form = EmployeeForm(instance=e)
return render_to_response('employee_details.html', {'form': form}, context_instance,)
also changed template form action to "" (from /newemp/ which was the correct location for my new employee tempalte, but not the update.
Thanks to this similar question.
updating a form in djnago is simple:
steps:
1. extract the previous data of the form and populate the edit form with this these details to show to user
2. get the new data from the edit form and store it into the database
step1:
getting the previous data
views.py
def edit_user_post(request, topic_id):
if request.method == 'POST':
form = UserPostForm(request.POST)
if form.is_valid():
#let user here be foreign key for the PostTopicModel
user = User.objects.get(username = request.user.username)
#now set the user for the form like: user = user
#get the other form values and post them
#eg:topic_heading = form.cleaned_data('topic_heading')
#save the details into db
#redirect
else:
#get the current post details
post_details = UserPostModel.objcets.get(id = topic_id)
data = {'topic_heading':topic.topic_heading,'topic_detail':topic.topic_detail,'topic_link':topic.topic_link,'tags':topic.tags}
#populate the edit form with previous details:
form = UserPostForm(initial = data)
return render(request,'link_to_template',{'form':form})