Django admin - store small pieces of data - python

I am quite new to django.
I have one problem to solve.
My website has couple of places where i would like to put some content (static text) managed by admin panel.
I know that there is django flatpage or some project like django-chunk. Flatpage has only couple of fields and django-chunk has all in one place (key-value).
I would like to add to panel admin new position only with edit view where user can fill couple of text fields. After save data is storied in file (or in database).
Generally i would like to store small pieces of text like phone number, address, some tagline which are on several pages. And creating model with full CRUD is unnecessary.
How can i do this? Is it possible?

You mention that creating a model is unnecessary, but not why. I think it is the right way to go.
class Snippet(models.Model):
name = models.CharField(max_length=24)
body = models.TextField()
Register it with the admin, and create as many snippets as you need. You could disable add and delete permissions if you only want your users to be able to edit the ones you've specified.
Add them to the template context:
def get_context_data(self, request, *args, **kwargs):
data = super(MyView, self).get_context_data(request, *args, **kwargs)
data['snippet1'] = Snippet.objects.get(name='snippet1').body
data['snippet2'] = Snippet.objects.get(name='snippet2').body
return data
Then print them in the template:
...
Contact us at {{ snippet1 }}!
...
This is a bit simplified. You may want a view mixin that adds all the snippets you need based on a declarative list of names, you probably want to optimize the queries so you don't loop through and hit the database 10 times per request, but this is a good start to a simple solution.

Related

Django: override form validation to get around this error

My specific problem is detailed below, but the main point of my question is this: When I click "Save" on the Django admin form while editing a model, what happens? What is the order of the validation/save process? How can I override these methods to solve my problem below, which is to say updating an intermediate table at the appropriate time as a workaround for the issue I described?
I'm working on a Django application which is facing a PostgreSQL database. The database is relatively large, and I have a lot of many-to-many relationships. The one that I am currently having an issue with is the n:m relationship between People and Sources.
The app is used mainly by non-technical people to add to the database through Django's built-in admin feature. The primary issue that I was having when I started working on the app was severe page slowdown when editing a Person on the admin site. If the person had too many Sources, each drop-down in the TabularInline representation of sources loaded thousands of entries. I found a workaround and overrode the get_formfield_for_foreignkey() method as follows:
def formfield_for_foreignkey(self, db_field, request, **kwargs):
field = super(SourceMaterialInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
# We will overwrite this method to ONLY return relevant materials on this person
materials = SourceMaterial.objects.all()
materials_p = SourceMaterial.people.through.objects.all()
person_ID = get_ID_from_path(request.path)
if not_integer(person_ID):
return field
materials_pid = materials_p.filter(person_id=person_ID)
# The next step is to use these relevant materials to obtain the SourceMaterial objects from the SourceMaterial table
relevant_ids = []
for mat in materials_pid:
relevant_ids += [mat.sourcematerial_id]
relevant_materials = materials.filter(id__in=relevant_ids)
# now we can set the queryset so that only relevant materials are included
field.queryset = relevant_materials
return field
So this accomplishes what I wanted. When you go to edit a Person object through the Admin page, the dropdown menus to select sources only load and display Sources that belong to that Person.
The problem this creates, however, is when I go to add a new Source via the Person change form. I would like to keep this functionality, but it is now broken.
If I go to add a new source, a pop-up window appears. I select the file on my machine, give it a name and click add. At this point it is added to the Sources table, but not to the intermediate table linking it to this Person. When I go to save the Person, the form asks me to correct this error: Select a valid choice. That choice is not one of the available choices.
Here is the pop-up: Add new source material pop-up for clarity
This makes sense, as there is no established link between the Person and the Source yet. So instead I try adding the Person ID as well when adding a source this way, and the error is: Sourcematerial-person relationship with this Sourcematerial and Person already exists. I'm assuming this is because when I add the Source with the Person ID, the intermediate table is populated appropriately, and when I go to save, it tries to add the source again, because the form detects it as being new.
I'm hoping I can override form validation/saving so that I can keep functionality of adding a Source to the database this way. If anybody has any suggestions as to what exactly I could override (I was very confused by the docs) or another work around, that'd be great. Thanks!

Django - Form across multiple views with progress saving

I'm working on a Django project and to make the forms experience far smoother I want to spread a ModelForm across a few pages. It would be ideal if users who are logged in can save their progress in the form without actually posting the content (in this case, a JobApplication where users can come back to filling in info without actually sending off the application).
Currently I have looked at other answers on SO such as this one; but this only shows me how to utilise caching to store information between the views where the form is present.
Models.py (models, forms and views have been simplified for readability):
class JobApplication(models.Model):
job = models.ForeignKey(JobPost,on_delete=models.SET_NULL,...)
user = models.ForeignKey(AUTH_USER_MODEL,...)
details = models.CharField(max_length=300)
skills = models.CharField(max_length=300)
feedback = models.CharField(max_length=300)
#... [insert more fields] ...
Forms.py:
class Application(forms.ModelForm):
details = forms.CharField() # To go in page 1 of the form process
skills = forms.CharField() # To go in page 2
feedback = forms.CharField() # To go in page 3
class Meta:
model = JobApplication
fields = ['details','skills','feedback']
Views.py:
from . import forms
def view1(request):
form = forms.Application()
if request.method == 'POST':
form = forms.Application(data=request.POST)
... some logic here which I am not sure of ...
return render(request, 'view1.html', {})
def view2(request):
form = forms.Application()
if request.method == 'POST':
form = forms.Application(data=request.POST)
...
return render(request, 'view2.html', {})
def view3(request):
form = forms.Application()
if request.method == 'POST':
form = forms.Application(data=request.POST)
...
return render(request, 'view3.html', {})
Note that I'm happy to edit my forms or models to achieve this multi-page, save progress effect that you may see on job sites.
Let me know if there's any more code I can add that will be useful, since I'm not too sure what else will be required.
Thanks!
I had a similar use case in my application what I did was created
multiple forms out the models and a central view controlling the
progress of the form.
The view has a list of forms it has to propagate through
GET : /form/<index> => form/0
POST : Save data to the form
Initially the form will have no initial data, for index > 0 the
initial data will be the previously saved model object
When user clicks on next increment the URL index counter, Decrease
it for prev, Don't save anything on skip
Here is a gist of how it would look.
https://gist.github.com/bhavaniravi/b784c57ae9d24fce359ae44e2e90b8e3
I don't know if this is the best optimized method of all times but this is what I did. Any suggestions on improvement is most welcomed
You will need a Form for each action you need. With it on hands, you can use a feature from Django 1.7 called Form Wizard (Yes, it is built in), the best way achieving this is using Class-Based Views, that is way more flexible, clean and cohesive than FBV in this case.
https://docs.djangoproject.com/en/1.7/ref/contrib/formtools/form-wizard/#
Basically you will define a list of steps and forms, both tied to the same URL. You can use customized templates for each form:
https://docs.djangoproject.com/en/1.7/ref/contrib/formtools/form-wizard/#using-a-different-template-for-each-form
[EDITED]
As jayt said in comments, the formtools was deprecated since version 1.8 and is now apart from core package and can be found in https://github.com/django/django-formtools/
Good luck. =)
I'm working on a Django project and to make the forms experience far
smoother I want to spread a ModelForm across a few pages. It would be
ideal if users who are logged in can save their progress in the form
without actually posting the content (in this case, a JobApplication
where users can come back to filling in info without actually sending
off the application).
You are mixing UI/UX concepts with technical implementation here.
Your overall process is a job application where you have these states:
create a new applicant (user) if not yet done
create a new application for that user
work on that application until the user says they are done (allowed to span multiple (browser) sessions)
mark application as done -> actually apply to the job
How the data for the application is collected (in whatever forms or such) and "submitted" in a web development sense is independent of the actual job application action - it just need to happen beforehand.
So rather than just use one single Django form, you have these possibilities:
(A) Create smaller models that represent a certain content section of the form and that should get their own form. Create ModelForms for those and make them accessible in their own views.
(B) Stick with the single model as you have now but create custom Django forms for each of the separate pages as you have planned. You can still use ModelForm and model=JobApplication but each form has to specify the set of fields it covers and validate only these fields.
Progress Tracking
(A) or (B): You could track how much information has been input (in percent, e.g. by simply counting all fields and all non-empty fields and calculating their percentage.
(A): With different django models you could add a last modified timestamp to each model and show if and when this section was edited.
Main Page
In an overview page you could collect all the data for the user so that they can see what the application will look like (to someone else maybe even) - and there they can also click a button "apply to the job!", or they can open each specific form if they see that any of the data is not yet complete.
Actual Job Application
Once the user clicks on "apply to the job" a different POST happens that for example sets a datetime field on the JobApplication model. This way you can identify all applications that have reached this process step.
Same with any other steps that happen after that. Maybe the user can create another application by copying an existing one. Or their accepted status is also entered in that system and they can log in and check that. Or the system sends out email notifications or similar. Just add fields for anything that is of interest, then you can also reflect that in the UI if you want to.

Django ModelForm segmentation

I've got a model with quite a few fields in it (40 to be exact) and would like to segment the Django form into reusable snippets such that they can be combined or disused dependant on the view.
For example. Let's say a user is on his profile page. He would should probably have the full form. However, what if his guardian only has access to (for example) the ability to set his allergies?
It seems like it would violate DRY to create two separate forms in order to generate the guardian view of this user.
Would it be prudent to create a form for each segment that I would naturally see, then extend all of those into a main form?
class AllergiesForm(forms.form):
""" allergies fields
"""
class ContactForm(forms.form):
""" contact info fields
"""
class AllUserInfoForm(forms.Form, AllergiesForm, ContactForm):
""" what would even go in here if all the other forms
are handling their own fields for _clean etc?
"""
That way, given a specific view I could show only the pertinent fields OR I could pull AllUserInfoForm from my view and get all of it.
Or do I need to create templates that would manually interact with this form for only the given fields? And if that is the case I suppose then the _clean methods would need to check permissions before allowing any actual cleaning.

Django Save Incomplete Progress on Form

I have a django webapp with multiple users logging in and fill in a form.
Some users may start filling in a form and lack some required data (e.g., a grant #) needed to validate the form (and before we can start working on it). I want them to be able to fill out the form and have an option to save the partial info (so another day they can log back in and complete it) or submit the full info undergoing validation.
Currently I'm using ModelForm for all the forms I use, and the Model has constraints to ensure valid data (e.g., the grant # has to be unique). However, I want them to be able to save this intermediary data without undergoing any validation.
The solution I've thought of seems rather inelegant and un-django-ey: create a "Save Partial Form" button that saves the POST dictionary converts it to a shelf file and create a "SavedPartialForm" model connecting the user to partial forms saved in the shelf. Does this seem sensible? Is there a better way to save the POST dict directly into the db? Or is an add-on module that does this partial-save of a form (which seems to be a fairly common activity with webforms)?
My biggest concern with my method is I want to eventually be able to do this form-autosave automatically (say every 10 minutes) in some ajax/jquery method without actually pressing a button and sending the POST request (e.g., so the user isn't redirected off the page when autosave is triggered). I'm not that familiar with jquery and am wondering if it would be possible to do this.
before Saving:
for field in form.fields:
form.fields[field].required = False
then:
form.save()
The issue is that you have multiple Forms.
Partial. Incomplete. Complete. Ready for this. Ready for that.
Indeed, you have a Form-per-stage of a workflow.
Nothing wrong with this at all.
Figure out where in the workflow you are.
Populate and present the form for the next stage.
Forms can inherit from each other to save repeating validation methods.
Place the following into your form __init__
for field in form.fields:
form.fields[field].required = False
For example:
class MySexyForm(Form):
def __init__(self, *args, **kwargs):
super(MySexyForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
Then call:
form = MySexyForm(...)
form.save()
However you'll need to make sure your clean() method can handle any missing attributes by conditionally checking if they exist in cleaned_data. For example, if another form field validation relies on customer_id but your partial form have not specified one, then customer_id would not be in cleaned_data.
If this is for a model form, you could check if the value was in cleaned_data, and fallback onto instance.field if it was missing, for example;
def clean(self):
inst = self.instance
customer_id_new = self.cleaned_data.get('customer_id', None)
customer_id_old = getattr(self.instance, 'customer_id') if inst else None
customer_id = customer_id_new if customer_id_new else customer_id_old
Remember that the value new value will almost certainly not be in the same format as the old value, for example customer_id could actually be a RelatedField on the model instance but an pk int on the form data. Again, you'll need to handle these type differences within your clean.
This is one area where Django Forms really are lacking sadly.

Multiple versions of django admin page for the same model

In my django admin section, I'd like to show different versions of the admin page depending on what kind of user is currently logged in. I can think of a couple ways this might work, but haven't figured out how to do any of them.
Perhaps I could put logic into the admin.ModelAdmin to look at the current user and change the 'exclude' field dynamically. Does that work? Or maybe run different custom templates based on who's logged in, and have the templates include / exclude the fields as appropriate.
I could register two versions of the admin.ModelAdmin class, one for each type of user, and maybe restrict access through permissions? But the permissions system seems to believe fairly deeply in one set of permissions per model class so I'm not sure how to change that.
I could grab a couple of the widgets that are used in rendering the admin page templates, and include them in my own page that does the one specific job I need powerful users to be able to do.
I could set up multiple AdminSites and restrict access to them through the url / view system. But then I'm not sure how to register different admin.ModelAdmin classes with the different AdminSites.
Any advice on this would be appreciated.
Answer
Thanks for the hint. Here's how I did it...
def get_form(self, request, obj=None, **kwargs):
"""This dynamically inserts the "owners" field into the exclude list
if the current user is not superuser.
"""
if not request.user.is_superuser:
if self.exclude:
self.exclude.append('owners')
else:
self.exclude = ['owners']
else:
# Necessary since Admin objects outlive requests
try:
self.exclude.remove('owners')
except:
pass
return super(OwnersModelAdmin,self).get_form(request, obj=None, **kwargs)
There are quite a few hooks provided in the ModelAdmin class for this sort of thing.
One possibility would be to override the get_form method. This takes the request, as well as the object being edited, so you could get the current user from there, and return different ModelForms dependent on the user.
It's worth looking at the source for ModelAdmin - it's in django.contrib.admin.options - to see if overriding this or any other other methods might meet your needs.

Categories