Related
Recently I've decide to create form creation form in Flask web app. After searching form creation found Formfield, FieldList classes in flask wtf forms and I can create the form with these classes. but it doesn't provide that I want to.
First- I am going to create a form creation form which will be help me to create form and fields on management interface.
Second- I want to be able to add the fields, not the same type of field, all different kind, such as (booleanField, StringField, IntegerField, DateTimeField etc.) because, in the form there could be different type of fields for specific reason.
Third- I want to retreive this form whenever I want to use in my view
On the DB models side;
class Form(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.StringField)
fields = db.relationship('FormFields', backref='forms', lazy=True)
class FormFields(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.StringField)
field_type = db.Column(db.StringField)
form_id = db.Column(db.Integer, db.ForeignKey('forms.id'), nullable=False)
And othr tables for StrinField, BooleanField, TextField, etc. etc. should be connected this field model, and when I save the data over this created form, these data should be saved int the correct tables
The reason I am searching this, because I don't want to hardcode the Forms and fields in the code, when I need to new form or field I don't want to update code itself, it should be dynamically updated on the database.
And I want to use sqlalchemy based form creation from management page. And this will help to create anytime new form and relate the fields to the form. And on the internet still I didn't find the these style form creation for Flask, almost all of them creating dynamic for with same type of fields
Any ideas?
Last a couple of weeks I was search how to create dynamically flask form based on models
And #nick-shebanov has been redirect me to another approach EAV impelemntation, which is really diffucult to implement. I've tried :)
And decide to create form based on dictionary, and intend to populate the related attributes from the model and pass it to form as dictionary.
What I've done so far;
# app.py file
from flask_wtf import Form
from flask import Flask, render_template, request, flash
from flask_wtf import FlaskForm
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from wtforms import TextField, IntegerField, HiddenField, StringField, TextAreaField, SubmitField, RadioField,SelectField
from wtforms import validators, ValidationError
app = Flask(__name__)
app.secret_key = 'secret123'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sqlite.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)
migrate = Migrate(app, db)
# form class with static fields
class DynamicForm(FlaskForm):
form_type = HiddenField(default='FormType', render_kw={ 'type':'hidden' })
# name = StringField()
#app.route('/', methods=['GET', 'POST'])
def index():
fields = {
'username': 'Username',
'first_name': 'Fisrt Name',
'last_name': 'Last Name',
'email': 'Email',
'mobile_phone': 'Mobile Phone'
}
for key, value in fields.items():
setattr(DynamicForm, key, StringField(value))
form = DynamicForm()
if request.method == 'POST':
# print(dir(request.form))
# print(request.form)
dict = request.form.to_dict()
# print(dict.keys())
print(request.form.to_dict())
return render_template('index.html', form=form)
if __name__ == '__main__':
app.run(debug = True)
# index.html template
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<form method="POST">
{{ form.csrf_token }}
{{ form.form_type }}
{% for field in form if field.name != 'csrf_token' %}
{% if field.name != 'form_type' %}
<div>
{{ field.label() }}
{{ field() }}
{% for error in field.errors %}
<div class="error">{{ error }}</div>
{% endfor %}
</div>
{% endif %}
{% endfor %}
<input type="submit" value="Go">
</form>
</body>
</html>
Now I can see my all fields has been rendered including hidden field. When I fill the form and post the data, I can capture it.
But still I didn't achieve to implement save the captured data into database like vertical DB modelling style yet
Here is my simple approach of DB modelling
see image here
Is there suggestions?
I've created a custom admin view as documented here.
class MyAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super().get_urls()
my_urls = [
path('stats/', self.admin_site.admin_view(self.stats)),
]
return my_urls + urls
def stats(self, request):
request.current_app = self.admin_site.name
context = dict(
# Include common variables for rendering the admin template.
self.admin_site.each_context(request),
# Anything else you want in the context...
key='blah',
)
return TemplateResponse(request, "sometemplate.html", context)
The URL is working and the template is loading.
But how, can I get a link to my new custom view into the overview of the Django admin?
There is already a similar question How do you add a new entry into the django admin index?, but all of the provided answers were not very satisfying for me.
Therefore I ran through the source code and was looking for a possibility which wouldn't involve overriding any templates, neither index.html nor app_index.html.
The file django/contrib/admin/sites.py holds the code responsible for rendering index.html and app_index.html, the first is the template that displays what is illustrated in the question.
The method index renders the template index.html and displays the available apps with the registered model admins. It uses the method get_app_list to get the apps. Within this method the method _build_app_dict is called, which gets the models and the model admins.
The method app_index renders the template app_index.html and displays the registered model admins for a single app. It uses the method _build_app_dict, mentioned before.
Thus I decided to override this method in my custom admin. Based on the example in the question it can look like this (differences to the original example are shown in bold):
class MyAdmin(admin.AdminSite):
def get_urls(self):
urls = super().get_urls()
my_urls = [
path('stats/', self.admin_site.admin_view(self.stats), name='stats'),
]
return my_urls + urls
def stats(self, request):
request.current_app = self.admin_site.name
context = dict(
# Include common variables for rendering the admin template.
self.admin_site.each_context(request),
# Anything else you want in the context...
key='blah',
)
return TemplateResponse(request, "sometemplate.html", context)
def _build_app_dict(self, request, label=None):
# we create manually a dict to fake a model for our view 'stats'
# this is an example how the dict should look like, i.e. which keys
# should be present, the actual values may vary
stats = {
'name': 'Stats',
'admin_url': reverse('my_admin:stats'),
'object_name': 'Stats',
'perms': {'delete': False, 'add': False, 'change': False},
'add_url': ''
}
# get the app dict from the parent method
app_dict = super(MyAdmin, self)._build_app_dict(request, label)
# check if there is value for label, then the app_index will be rendered
if label:
# append the manually created dictionary 'stats'
app_dict['models'].append(stats)
# otherwise the index will be rendered
# and we have to get the entry for our app,
# which is in this case 'traffic'
# using TrafficConfig.name or TrafficConfig.label
# might be better than hard coding the value
else:
app = app_dict.get('traffic', None)
# if an entry for 'traffic' has been found
# we append our manually created dictionary
if app:
app['models'].append(stats)
return app_dict
my_admin = MyAdmin(name='my_admin')
my_admin.register(Traffic)
Now when we open our custom admin we'll see something like this:
TRAFFIC
---------------------------------
Traffics + Add \ Change
Stats \ Change
This is because we manipulated the dictionary used to render the template and it uses the values we specified, in this case the most relevant is the name Stats, the admin_url which will call the custom view stats. Since we left add_url empty, there will be no + Add link displayed.
Important is also the penultimate line, where we call our custom admin and pass it a name, it will be used as a url namespace.
EDIT:
Unfortunately I noticed only after posting the answer that the question is asking how to display a link for a custom view created in a ModelAdmin, whereas my answer explains how to do this for a custom admin AdminSite. I hope it still of some help.
I know this is a bit old but I had the same issue and I found a simpler (yet maybe messier) way of doing it, that doesn't involve overriding templates or methods.
I just created a proxy model in models.py such as:
class Proxy(models.Model):
id = models.BigAutoField(db_column='id', primary_key=True)
def __str__(self):
return "<Label: id: %d>" % self.id
class Meta:
managed = False
verbose_name_plural = 'proxies'
db_table = 'proxy'
ordering = ('id',)
Which is just a mysql view that a created from am existing table
create view proxy
as select id
from samples
LIMIT 10;
And finally in admin.py
#admin.register(Proxy)
class LabelAdmin(admin.ModelAdmin):
change_list_template = 'label_view.html'
def changelist_view(self, request, extra_context=None):
...
return render(request, "label_view.html", context)
Although not a good way, but you can try overriding the default index.html of the django admin as :
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %}
<th scope="row">{{ model.name }}</th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.add_url %}
<td>{% trans 'Add' %}</td>
{% else %}
<td> </td>
{% endif %}
{% if model.admin_url %}
<td>{% trans 'Change' %}</td>
{% else %}
<td> </td>
{% endif %}
</tr>
{% endfor %}
<!-- extra link you want to display -->
<tr>
<th scope="row">link_name</th>
<td>{% trans 'Change' %}</td>
</tr>
After overriding this block put index.html inside your templates/admin directory of your project. Hope this helps.
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'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})
The Setup:
I'm working on a Django application which allows users to create an object in the database and then go back and edit it as much as they desire.
Django's admin site keeps a history of the changes made to objects through the admin site.
The Question:
How do I hook my application in to the admin site's change history so that I can see the history of changes users make to their "content"?
The admin history is just an app like any other Django app, with the exception being special placement on the admin site.
The model is in django.contrib.admin.models.LogEntry.
When a user makes a change, add to the log like this (stolen shamelessly from contrib/admin/options.py:
from django.utils.encoding import force_unicode
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin.models import LogEntry, ADDITION
LogEntry.objects.log_action(
user_id = request.user.pk,
content_type_id = ContentType.objects.get_for_model(object).pk,
object_id = object.pk,
object_repr = force_unicode(object),
action_flag = ADDITION
)
where object is the object that was changed of course.
Now I see Daniel's answer and agree with him, it is pretty limited.
In my opinion a stronger approach is to use the code from Marty Alchin in his book Pro Django (see Keeping Historical Records starting at page 263). There is an application django-simple-history which implements and extends this approach (docs here).
The admin's change history log is defined in django.contrib.admin.models, and there's a history_view method in the standard ModelAdmin class.
They're not particularly clever though, and fairly tightly coupled to the admin, so you may be best just using these for ideas and creating your own version for your app.
I know this question is old, but as of today (Django 1.9), Django's history items are more robust than they were at the date of this question. In a current project, I needed to get the recent history items and put them into a dropdown from the navbar. This is how I did it and was very straight forward:
*views.py*
from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
def main(request, template):
logs = LogEntry.objects.exclude(change_message="No fields changed.").order_by('-action_time')[:20]
logCount = LogEntry.objects.exclude(change_message="No fields changed.").order_by('-action_time')[:20].count()
return render(request, template, {"logs":logs, "logCount":logCount})
As seen in the above code snippet, I'm creating a basic queryset from the LogEntry model (django.contrib.admin.models.py is where it's located in django 1.9) and excluding the items where no changes are involved, ordering it by the action time and only showing the past 20 logs. I'm also getting another item with just the count. If you look at the LogEntry model, you can see the field names that Django has used in order to pull back the pieces of data that you need. For my specific case, here is what I used in my template:
Link to Image Of Final Product
*template.html*
<ul class="dropdown-menu">
<li class="external">
<h3><span class="bold">{{ logCount }}</span> Notification(s) </h3>
View All
</li>
{% if logs %}
<ul class="dropdown-menu-list scroller actionlist" data-handle-color="#637283" style="height: 250px;">
{% for log in logs %}
<li>
<a href="javascript:;">
<span class="time">{{ log.action_time|date:"m/d/Y - g:ia" }} </span>
<span class="details">
{% if log.action_flag == 1 %}
<span class="label label-sm label-icon label-success">
<i class="fa fa-plus"></i>
</span>
{% elif log.action_flag == 2 %}
<span class="label label-sm label-icon label-info">
<i class="fa fa-edit"></i>
</span>
{% elif log.action_flag == 3 %}
<span class="label label-sm label-icon label-danger">
<i class="fa fa-minus"></i>
</span>
{% endif %}
{{ log.content_type|capfirst }}: {{ log }}
</span>
</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>{% trans "This object doesn't have a change history. It probably wasn't added via this admin site." %}</p>
{% endif %}
</li>
</ul>
To add to what's already been said, here are some other resources for you:
(1) I've been working with an app called django-reversion which 'hooks into' the admin history and actually adds to it. If you wanted some sample code that would be a good place to look.
(2) If you decided to roll your own history functionality django provides signals that you could subscribe to to have your app handle, for instance, post_save for each history object. Your code would run each time a history log entry was saved. Doc: Django signals
Example Code
Hello,
I recently hacked in some logging to an "update" view for our server inventory database. I figured I would share my "example" code. The function which follows takes one of our "Server" objects, a list of things which have been changed, and an action_flag of either ADDITION or CHANGE. It simplifies things a wee bit where ADDITION means "added a new server." A more flexible approach would allow for adding an attribute to a server. Of course, it was sufficiently challenging to audit our existing functions to determine if a changes had actually taken place, so I am happy enough to log new attributes as a "change".
from django.contrib.admin.models import LogEntry, User, ADDITION, CHANGE
from django.contrib.contenttypes.models import ContentType
def update_server_admin_log(server, updated_list, action_flag):
"""Log changes to Admin log."""
if updated_list or action_flag == ADDITION:
if action_flag == ADDITION:
change_message = "Added server %s with hostname %s." % (server.serial, server.name)
# http://dannyman.toldme.com/2010/06/30/python-list-comma-comma-and/
elif len(updated_list) > 1:
change_message = "Changed " + ", ".join(map(str, updated_list[:-1])) + " and " + updated_list[-1] + "."
else:
change_message = "Changed " + updated_list[0] + "."
# http://stackoverflow.com/questions/987669/tying-in-to-django-admins-model-history
try:
LogEntry.objects.log_action(
# The "update" user added just for this purpose -- you probably want request.user.id
user_id = User.objects.get(username='update').id,
content_type_id = ContentType.objects.get_for_model(server).id,
object_id = server.id,
# HW serial number of our local "Server" object -- definitely change when adapting ;)
object_repr = server.serial,
change_message = change_message,
action_flag = action_flag,
)
except:
print "Failed to log action."
Example code:
from django.contrib.contenttypes.models import ContentType
from django.contrib.admin.models import LogEntry, ADDITION
LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=ContentType.objects.get_for_model(object).pk,
object_id=object.pk,
object_repr=str(object),
action_flag=ADDITION,
)
Object is the object you want to register in the admin site log.
You can try with str() class in the parameter object_repr.