Django: user interface for changing objects foreign key - django

Let's say I have a simple set of Django models related by a ForeignKey:
class Author(models.Model):
name = models.CharField('Name', max_length=50)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField('Title', max_length=50)
rank = models.IntegerField('Rank')
Now in my template I want to create a user interface with two <ol> lists side by side, each list represents the list of Books for a given Author ordered by their rank, and has a form for the Book in the <li> node. Thus the page as a whole consists of an Author formset, each of which consists of an inline Book formset. Hope that is clear...
Now with some fancy javascript/jQuery I allow the user to do two things: 1)she can drag-and-drop a Book form within its <ol> to set its rank, and 2)she can drag-and-drop a Book from one <ol> to the other <ol> with the purpose of changing its Author.
From the Django point of view, there is absolutely no trouble form me to accomplish task 1. The formsets are submitted, the data saved, and Django doesn't know the difference between this method and if the user had simply entered different book ranks in the input text field of course.
However, accomplishing task 2 seems to me a little trickier. If I drag-and-drop a Book form from one Author list to the other, and then use some more fancy javascript to update its input class ids and names(id_1-book-0-title --> id_2-book-0-title for example), along with updating the TOTAL_FORMS and INITIAL_FORMS of the Book formset Management forms to reflect the new number of Book forms in each row, then things don't quite work.
Django treats the dragged form in the list as a new form of course, and creates a genuinely new Book in the database(problematic if you have unique fields since this Book is already in the database). As for the absence of this form in the oldlist, the forms DELETE is not sent and so Django doesn't end up deleting the old object of course. Without any unique fields the result is you get two copies of the Book in the database, one in each Author's list now. With unique fields of some kind of the Book model (e.g. a serial number say) you just hit validation errors.
Does anyone know what the right way to set this up would be?
EDIT: here is the rough view for what I have so far:
def managerView(request):
if request.method == "POST":
author_formset = AuthorFormSet(request.POST, prefix='author')
pairs =[]
for authorform in author_formset:
author=authorform.instance
tempDict={}
tempDict['authorform'] =authorform
tempDict['bookformset'] = BookFormSet(request.POST, prefix=str(author.pk)+'-book', instance=author)
pairs.append(tempDict)
if author_formset.is_valid() and all([pair['bookformset'].is_valid() for pair in pairs]):
author_formset.save()
for pair in pairs:
author=pair['authorform'].instance
#For this author, delete all current books, then repopulate
#from forms in book list that came from request.
old_book_pks = set([book.pk for book in author.books.all()])
new_book_pks = set([bform.instance.pk for bform in pair['bookformset'].forms])
pks_for_trash = old_book_pks - new_book_pks
if len(pair['bookformset'].forms): pair['bookformset'].save()
return HttpResponseRedirect(reverse('admin:index'))
else:
author_formset = AuthorFormSet(prefix='author', queryset=Author.objects.order_by('rank'))
pairs=[]
for f in author_formset:
author=f.instance
#prefix the different book formsets like '1-book' etc
pairs.append({'authorform':f, 'bookformset': BookFormSet(prefix=str(author.pk)+'-book',instance=author)})
myContext= {'authorformset': author_formset, 'pairs':pairs, 'request': request}
return myContext
Now the formsets:
AuthorFormSet = modelformset_factory(Author, extra=0)
BookFormSet = inlineformset_factory(Author, Book, form=BookForm, extra=0, formset=BaseBookFormSet)
Not much going on in the BookForm and BaseBookFormSet except some custom cleaning, so I won't include them just yet, unless anyone thinks they would be useful.

It would be helpful if you also included your form and view code. However, as a general concept this doesn't seem like it should be too hard to implement. Are you using class-based views? It sounds like one way to think about what's going on is that Creation logic is being triggered when you'd rather have Update logic being triggered. CBVs are designed for exactly this sort of thing. In terms of your models, you need to pass a reference to the Book instance's PK up to an Update View (either a Class-based or Functional view) along with the PK of the new Author.
Okay, without actually getting this code running locally, it's hard to know if this will exactly solve your problem, but I think the crux is:
for pair in pairs:
author=pair['authorform'].instance
#For this author, delete all current books, then repopulate
#from forms in book list that came from request.
old_book_pks = set([book.pk for book in author.books.all()])
new_book_pks = set([bform.instance.pk for bform in pair['bookformset'].forms])
pks_for_trash = old_book_pks - new_book_pks
if len(pair['bookformset'].forms): pair['bookformset'].save()
return HttpResponseRedirect(reverse('admin:index'))
Have you tried something like this:
for pk in new_book_pks:
book = Book.objects.get(pk=pk)
book.author = author
book.save()
?
Also, just a note:
if len(pair['bookformset'].forms): pair['bookformset'].save()
Personally, this looks unpythonic to me. The single line conditional probably violates PEP8. Is there a reason why you're using len(pair...)? Are you not able to just do if pair['bookformset'].forms: ?

I realised I was being quite silly. If instead of insisting on using inline formsets for the Books, I just send back a formset of all Books (regardless of Author) and another of all Authors, then every Book has an Author drop down which is trivial to update with javascript upon dragging and dropping it into a new list (this field could made hidden for presentation purposes). Everything then just works upon save as it should.
For the problem of organising the right Books into the right Author <ol> in such a setup, a small template tag filter does the job:
#register.filter(name='forms_for_author')
def forms_for_author(book_formset, authorid):
forms = []
for form in book_formset:
if str(form.instance.tap_id) == str(tapid):
forms.append(form)
return forms
Used in template as
{% for authorform in authorformset %}
<ol class="author">
{% for bookform in bookformset|forms_for_author:authorform.id.value %}
<li>..</li>
{% endfor %}
</ol>
{% endfor %}

Related

Should Django Querysets Be Called in the Templates or Passed Into The Template Context?

I may get blowback from asking this question but it's something I've wondered about for a while so here goes.
In a Django template, you can use .all to get the queryset of things, like so:
{% for prerequisite in object.prerequisites.all %}
<li class="list-group-item">{{ prerequisite.text }}</li>
{% endfor %}
The model in this case looks like this, but the only relevant piece of information is that object above has a relation to the Prerequisite model through the ForeignKey.
class Prerequisite(models.Model):
content = models.ForeignKey(Content, on_delete=models.CASCADE,
related_name='prerequisites')
text = models.CharField(max_length=100)
def __str__(self):
return str(self.text)
My question is
Is it best-practice to call Django querysets in the template, (ie: object.prerequisites.all) or should one pass it in the view through the context?
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['prerequisites'] = self.object.prerequisites.all()
return context
It's really convenient to do querysets in the template, but it seems like it should be business logic located in the view. Does Django have a specific stance on this?
according to django documentation:
https://docs.djangoproject.com/en/4.1/#the-view-layer
The view layer -
Django has the concept of “views” to encapsulate the logic responsible for processing a user’s request and for returning the response.
https://docs.djangoproject.com/en/4.1/#the-template-layer
The template layer -
The template layer provides a designer-friendly syntax for rendering the information to be presented to the user.
i have different example, i know a lot of programmers loves to move buisness logic from views.py to serializer.py, even if original purpose of serialezers life is - to serialize
i believe the most appropriate way is to pass your data through context in the views.
Views originally is all your buisness logic, serializer suppose only serialization, and templates purpose is to show html page, python wasn't originaly desined to manipulate html page

Adding a new ManyToMany relationship in a form?

Django newbie here. I keep encountering the exact same design paradigm, which seems like it should be common for everyone, yet can't find out how it's supposed to be resolved.
Picture the following ManyToMany relationships:
An organization could have many members; each person could be a member of many organizations
An organization could manage many objects. An object could be in use by multiple organizations. The same applies to the relationship between people and objects.
An organization, person, or object could have multiple media elements (photos, videos, etc) of it, and a single media element could be tagged with numerous organizations, people, or objects
Nothing unusual. But how does a site user add a new person, organization, or object? It seems that if someone is filling out an "add an organization" form, in addition to choosing from existing people, objects, media, etc there should be a button for "new member", "new object", "new photo", etc, and this should take you to the forms for creating new members, objects, media, etc. And when they're done, it should go back to the previous page - whose form filled-out form entries should persist, and the newly created entry should be listed in its respective ManyToMany field.
The problem is, I don't know how to do this. I don't know how one would add a button in the middle of a form, and can't seem to find anything to clarify how to do it. I assume it would need to be a submit button, with a different name / id or some other way so that views.py can treat it differently, via flagging an "incomplete" record in the database. And the new form will need to be passed information about what page it needs to go back to when it's submitted.
Am I thinking about this correctly? If so, then I think the only knowledge I lack is how to add a second submit button in a form and how to recognize its usage in views.py.
If I'm not thinking about this correctly, however, please suggest an alternative paradigm that you think makes more sense :) This is my first Django project, so I'm learning as I do it.
ED: I'm thinking maybe instead of using {{ form.as_p }} to display it, I need to iterate over fields and use some logic to add the extra submit button in the middle as html: What's the best way to add custom HTML in the middle of a form with many fields?
Then I'll just need to figure out a way to detect which submit button was used and put some logic behind it to handle partially-submitted forms, redirecting to a form to create the relation, and then redirecting back on submit... I can probably figure this out...
The first thing I would recommend is to define your models. Lay them all out with the attributes you require. That'll be the foundation for everything else you want to accomplish. You can do everything you mentioned with Django... it's just a matter of coding it. As far as I know you would need to create each model instance separately, and then you can refer to already created instances in the create form for the Organization model for example. I would look into the docs for generic views that help you create objects easily. Then you can link to other create forms if you wish. I don't know how you can create multiple instances of different models in one form, and I don't think it would be the best way to do things even if you can. Here's an example of a model, a create form, a create view, and corresponding url:
# models.py
class Organization(models.Model):
name = models.CharField(max_length=100, null=True, blank=True)
# forms.py
class OrganizationForm(forms.ModelForm):
class Meta:
model = Organization
fields = ('name',)
def __init__(self, *args, **kwargs):
super(OrganizationForm, self).__init__(*args, **kwargs)
self.fields['name'].required = True
def clean(self):
cleaned_data = super(OrganizationForm, self).clean()
name = cleaned_data.get('name')
# views.py
class OrganizationCreateView(CreateView): # inherits from CreateView
form_class = OrganizationForm
template_name = 'create_org.html'
success_url = 'success'
def form_valid(self, form): # validate the form and save the model instance
org = form.save(commit=False)
org.save()
return redirect(reverse('redirect_url'))
# urls.py
from Project.apps.app_name import views as app_views
app_name = 'app_name'
urlpatterns = [
url(r'^create_org/$', app_views.OrganizationCreateView.as_view(), name='create_org'), # as_view() is used for class based views
# create_org.html
<form method="post">
{% crsf_token %}
{{ form.as_p }}
<a href="{% url 'app_name:create_person' %}>Create person</a> # You can link to other create views, and just style the link as a button.
<input type="submit" value="Submit">
</form>
Hope that helps.

How do I get django template to recognize manytomanyfield comparison?

In my project, I am currently trying to access a manytomany field and compare it to another. I have several cases where I am doing something similar and it is working . The difference in this case is that I am trying to essentially say if one of the values in this many to many field equals a value in this other manytomanyfield, then do something....
Here is my code...
Book(models.Model):
publisher = models.ManyToManyField(Name)
Userprofile(models.Model):
publisher = models.ManyToManyField(Name)
In my Django template I am trying to do something like...
{% If user_publisher in form.initial.publisher_set.all %}
{{ publisher.name }}
{% endif %}
The example above is a simplified version of what I'm trying to do....I'm essentially trying to compare the manytomany fields and if any of the values match, perform an action. I've been at this most of today and have tried several combinations.
If I do something like
{% if user in form.initial.publisher.all %}
This works fine. I'm struggling to try and figure out how I can compare manytomanyfields. I suspect the user query works fine because it's not a manytomanyfield.
I'm thinking my format is off. I have tried to use the _set to publisher and the user publisher and when I go so far as to print the output, I am actually seeing that both the user_publisher and publisher querysets are the same. However, my django template is not showing me any results. Thanks in advance for any thoughts.
I have surfed SO all afternoon as well as Google, but can't quite figure out what I'm doing wrong.
Here is more detail to my issue. I am currently doing a CreateView whereby I am trying to get an existing record by overriding get_initial as shown below:
class BookUpdateView(CreateView):
model = Book
form_class = Book
template_name = 'Book/update_book.html'
def get_initial(self):
initial = super(BookUpdateView, self).get_initial()
book = Book.objects.get(pk=self.kwargs["pk"])
initial = book.__dict__.copy()
initial.update({
"publisher": publisher.all(),
})
return initial
Because I am copying in these records as a starting point and this is a CreateView, I don't yet have a PK or ID to query from a get_context_data perspective. Not sure how to go about getting the data that I am copying in but have not yet saved. I am actually trying to figure out if a user has the same publisher via their user profile and was able to figure out the format for get_context_data as shown below:
def get_context_data(self, **kwargs):
context = super(BookUpdateView, self).get_context_data(**kwargs)
user_publisher = self.request.user.userprofile.publisher.all()
return context
I tried to do something like....
{% if user_publisher in form.initial.publisher_set.all %}
But the template never recognizes that these two in fact do match...
When I print the variables....
They both show...
<QuerySet [<Publisher: ACME Publishing>]>
<QuerySet [<Publisher: ACME Publishing>]>
But the template does not recognize that they are the same. The screen is not rendered as I would expect when using the template language above. No errors, but end result isn't what I would expect either. Thanks in advance for any additional thoughts.
After a day or two of thinking about this, was able to figure out how to grab the PK and then use it in the context so that I could leverage the information via the context and not the form directly.
def get_context_data(self, **kwargs):
context = super(UpdateProcedureView, self).get_context_data(**kwargs)
pk=self.kwargs["pk"]
publisher = Publisher.objects.filter(pk=pk).filter(individual_access=self.request.user).count()
return context
Then in the form I can say something like if publisher > 0 then do something fancy. Thanks for the suggestions along the way to help me think this through.

How to display multiple forms of a single model in Django templates?

I have this model Note:
class Note(models.Model):
category = models.ForeignKey(Category)
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=40)
text = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
And I want to display this form:
class NoteEditForm(forms.ModelForm):
class Meta:
model = Note
fields = ('title', 'text')
in a template, but I want it to appear for each existing Note object in the database (it has to be that way). I've done something like that but then I hardcoded the form, pointing to the edit view URL with each object pk as a parameter; but I'm sure it has to be a clearer way, just I haven't found it. Could you guys help me with that? Thanks!
The easiest way to do this is to use a formset (also see model formsets).
For your Note model and NoteEditForm you could do something like this. You'd usually put this wherever you've defined your NoteEditForm but it can go in another file, such as views.py.
from django.forms import modelformset_factory
NoteEditFormSet = modelformset_factory(Note, form=NoteEditForm)
Using NoteEditFormSet in a view and template is almost the same as using a regular form, but there are a few differences to be aware of if you want to do anything complicated so have a look at the docs (view and template). If that's not clear enough, add a few details of what you're trying to do in your view and template and I'll try to help.
By default the formset will use Note.objects.all() as its queryset, which is what you say you want, but you can change that (details are covered in the docs).
Update:
To save an individual Note with an AJAX request I would add a second view to handle those requests. So if your formset for all Notes is served by a view at /notes/, you could add a view to handle your AJAX request at /notes/<note_id>/ (obviously just an example, adjust to fit your URL structure).
Then your JS on the /notes/ page is responsible for serializing the data for a single note and making the request to /notes/<note_id>/ (remember the CSRF token).
The HTML inputs generated for the formset have their IDs prefixed with something like id_form-<number>- and there are hidden inputs containing Note primary keys which will let you work out which ID prefix applies to each note.
I would think about doing it like this
{% for note in Notequeryset %}
<form action={% url 'url_name_to_form' pk={{note.pk}} %}>
{{form.as_p}}
</form>
{% endfor %}
Let me know what you think

Wrap a form and the realted inline_formeset with another formset

Sorry for the question title, i didn t know how to explain the question briefly.
Basicly i m on a situation like this:
models.py
class Author(Model):
...
class Book(Model)
author = models.ForeignKey(Author)
views.py
for author in Author.objects.filter(name=""):
author_form = AuthorForm(instance=author) #This is a model form
book_formset = inlineformset_factory(Author, Book, instance=author)
What i'd like to do now, is to create a formset of authors. Each element should contain an istance of AuthorForm and the related book_formset.
Any idea on how to do it??
Thanks
This person may have done what you are asking about but I don't think it's what you need.
If I understand you correctly, you are close, but should be using the factory (not the factory generator function) multiple times to create a list where each element has two separate items: the author form and the inline formset with the books. The key point being you will have two separate items rather than one inside the other.
Each form/inline formset will need a unique prefix to identify it relative to the others in the rendered html/form soup.
In your view:
AuthorBooksFormSet = inlineformset_factory(Author, Book)
author_books_list = list()
for author in author_queryset: #with whatever sorting you want in the template
prefix = #some unique string related to the author
author_form = AuthorForm(instance=author,
prefix='author'+prefix)
author_books_formset = AuthorBooksFormSet(instance = author,
prefix = 'books'+prefix)
author_books_list.append((author_form, author_books_formset))
Send the whole list to your template and:
{% for author_form, author_books_formset in author_books_list %}
...something with author_form
...something with author_books_formset
{% endfor %}
You may even be able to skip the author form if django provides a form for the instance object in the formset. But I've never used them so I'm not sure.
I guess you have moved on since I found this late through a google search, but what did you eventually do?