I'm trying to create a fairly simply input view for a django webapp. I have the following simply models set up. At the end I've included the traceback as well. The question is how do I create an object in the CreateView class and pass the foreign key of the parent object?
#models.py
#imports...
class Client(models.Model):
client_id = models.AutoField(
primary_key=True)
class Item(models.Model):
client = models.ForeignKey(
Client,
on_delete=models.CASCADE)
item_id = models.AutoField(
primary_key=True)
The idea is to have a list of unique clients and then each client can have a list of unique items. The items are linked to the client.
#views.py
#imports...
class ItemCreate(CreateView):
model = Item
fields = [
#list of fields
]
def form_valid(self, form):
form.instance.client_id = self.request.client.client_id
return super(PermCreate, self).form_valid(form)
Given these two class models, I'm trying to CreateView that will create a new Item and have it attached to the respective Client. I have a ListView that will iterate through Items for a given Client. The ListView has a link to the CreateView (Add New Item). I am not having a problem with the pk's in the views or even getting to the CreateView. I can't get the CreateView to save the object. I get an error that says...
'WSGIRequest' object has no attribute 'client'
The code above is derived from this question. I've tried several iterations of the argument to set form.instance.client_id but a request is likely the wrong call. The examples given are using user calls not per se the table foreign key information.
I've also tried this (using the primary keys for my models) and I've tired accessing the URL pk from the template tags - but figured if I can't access them in the the views object that getting from the template would be more difficult.
Traceback
File "/anaconda3/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
35. response = get_response(request)
File "/anaconda3/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
128. response = self.process_exception_by_middleware(e, request)
File "/anaconda3/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
126. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/anaconda3/lib/python3.6/site-packages/django/views/generic/base.py" in view
69. return self.dispatch(request, *args, **kwargs)
File "/anaconda3/lib/python3.6/site-packages/django/views/generic/base.py" in dispatch
89. return handler(request, *args, **kwargs)
File "/anaconda3/lib/python3.6/site-packages/django/views/generic/edit.py" in post
172. return super().post(request, *args, **kwargs)
File "/anaconda3/lib/python3.6/site-packages/django/views/generic/edit.py" in post
142. return self.form_valid(form)
File "/Users/billarmstrong/Documents/GitHub/Core/WebDataCollect/Pro/ProWP/views.py" in form_valid
55. form.instance.client_id = self.request.client.client_id
Exception Type: AttributeError at /ProWP/2/additem/
Exception Value: 'WSGIRequest' object has no attribute 'client'
Update
# urls.py
path('<int:pk>/additem/', views.ItemCreate.as_view(), name='item-add'),
path('<int:pk>/item/', views.ItemView.as_view(), name='itemview'),
I've also made some progress. I've started working with example 1 code and found that if I set form.instance.client_id = 2 that it will appropriately add the object with the foreign key of 2. So the issue is trying to get the originating POST pk. I've tried example 2 and it throws (1048, "column 'client_id' cannot be null") which i interpret to mean that I'm not getting the Item object. So, I tried example 3 and (1048, "Column 'client_id' cannot be null").
# views.py
# Example 1
def form_valid(self, form):
form.instance.client_id = 2
return super(PermCreate, self).form_valid(form)
# Example 2
def form_valid(self, form):
pk = self.kwargs.get("perm_id", None)
form.instance.client_id = pk
return super(PermCreate, self).form_valid(form)
# Example 3
def form_valid(self, form):
pk = self.kwargs.get("client_id", None)
form.instance.client_id = pk
return super(PermCreate, self).form_valid(form)
Update 2
after testing and printing - I think the issue is in my request or kwargs.get variable. Since the entire thing works when I hard code the client_id in the instance - I've concluded that the instance does in fact exist with all the appropriate information - including the URL primary key - but I'm not getting the right variable name to access it. I know it isn't item_id or client_id.
Update 3
Both request and KWARGS work. After working through every possible variable to get to the primary key it turned out to be pk.
So rather than using either the client_id or the item_id, the value is held in pk. Any explanation would be helpful. I'm guessing that the URL actually sets the variable from my urls.py file - but not 100 certain.
form.instance.client_id = self.request.client.client_id
this line should be like,
form.instance.client_id = self.request.POST['client'].client_id
or
form.instance.client_id = self.request.GET['client'].client_id
Depending upon request type.
Related
I'm making a Django app and I have a view that display both sides of an object_set (a reverse many to many). Because of this, I want to query all of the objects on both sides at the same time. Specifically speaking, I want to have all of the Signup objects that are associated with each Event.
(The view page format should look like this.)
Event (0)
-- Signup (0.0)
-- Signup (0.1)
-- Signup (0.2)
-- Signup (0.3)
Event (1)
-- Signup (1.0)
-- Signup (1.1)
Event (3)
-- Signup (3.0)
-- Signup (3.1)
-- Signup (3.2)
-- Signup (3.3)
...
The code is as follows:
class TournamentDetailView(DetailView):
model = Tournament
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tournament_id = self.get_object().pk
events = Event.objects.annotate(
cached_signups=(
Signup.objects
.filter(event_id=OuterRef('pk'), tournament_id=tournament_id, dropped=False)
.order_by('created')
.defer('tournament')
)
).all()
context['events'] = events
return context
Here's the traceback:
Traceback:
File "C:\Users\werdn\AppData\Local\Programs\Python\Python36-32\lib\site-packages\django\core\handlers\exception.py" in inner
35. response = get_response(request)
File "C:\Users\werdn\AppData\Local\Programs\Python\Python36-32\lib\site-packages\django\core\handlers\base.py" in _get_response
128. response = self.process_exception_by_middleware(e, request)
File "C:\Users\werdn\AppData\Local\Programs\Python\Python36-32\lib\site-packages\django\core\handlers\base.py" in _get_response
126. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\werdn\AppData\Local\Programs\Python\Python36-32\lib\site-packages\django\views\generic\base.py" in view
69. return self.dispatch(request, *args, **kwargs)
File "C:\Users\werdn\AppData\Local\Programs\Python\Python36-32\lib\site-packages\django\views\generic\base.py" in dispatch
89. return handler(request, *args, **kwargs)
File "C:\Users\werdn\AppData\Local\Programs\Python\Python36-32\lib\site-packages\django\views\generic\detail.py" in get
106. context = self.get_context_data(object=self.object)
File "C:\Users\werdn\PycharmProjects\gwspo-signups-website\gwhs_speech_and_debate\tournament_signups\views.py" in get_context_data
171. .defer('tournament')
File "C:\Users\werdn\AppData\Local\Programs\Python\Python36-32\lib\site-packages\django\db\models\manager.py" in manager_method
82. return getattr(self.get_queryset(), name)(*args, **kwargs)
File "C:\Users\werdn\AppData\Local\Programs\Python\Python36-32\lib\site-packages\django\db\models\query.py" in annotate
1000. if alias in annotations and annotation.contains_aggregate:
Exception Type: AttributeError at /tournaments/detail/lobo-howl/
Exception Value: 'Query' object has no attribute 'contains_aggregate'
I'm not sure why this is happening and it seems to be happening on the Signups.objects query but even with Signups.objects.all(), this Exception seems to be triggered. That leads me to believe that this is not an issue with the use of OuterRef('pk').
You can't just put a Query inside an annotation, since an annotation is like adding a column to the row you're fetching. Django supports the concept of a Subquery in an annotation, but that only works if you're fetching one aggregated value of the related model. This would work for example:
signups = Signup.objects
.filter(event_id=OuterRef('pk'), tournament_id=tournament_id, dropped=False)
.order_by('created')
.defer('tournament')
events = Event.objects.annotate(latest_signup=Subquery(signups.values('date')[:-1]))
If you just want to optimise database access so that you don't make a database query for each Event to fetch the related Signups, you should use prefetch_related:
events = Event.objects.all().prefetch_related('signups')
Since you didn't show how your models are defined, I'm assuming this is a reverse M2M relationship:
class Signup(models.Model):
events = models.ManyToManyField(to='Event', related_name='signups')
If you don't specify a related_name, the attribute to use for the prefetch is signup_set (which is not documented anywhere and very confusing since for aggregations it's the lowercase name of the model):
events = Event.objects.all().prefetch_related('signup_set')
This will make two queries: One for the Event objects, and only one extra for all the related Signup objects (instead of Event.objects.count() queries). The documentation for prefetch_related contains some useful information on how this works.
See #dirkgroten's as to why the question produces the error. Read on for an alternate method that solves the issue.
The reason why #dirkgroten's answer wouldn't work is that the model definition for Event doesn't include a value signups. However, since the ManyToMany relationship is defined on the Signup model, I can get the prefetch_related to work off of the Signup query, as we see below.
signups = Signup.objects.filter(
tournament_id=tournament_id
).prefetch_related(
'event',
...
)
context['signups'] = signups
context['events'] = signups.values('event', 'event__name', ...).distinct().order_by('event__name')
(Note that order_by is required in order for distinct to work and that values() returns a dict, not a queryset.)
If you want to query all Event/Signup pairs in your ManyToMany relationship, the most straightforward approach would be to query the helper table that stores just those pairs (as two ForeignKeys).
To get easy access to that table, you can make it a Django model by using the through option of ManyToManyField, see
https://docs.djangoproject.com/en/stable/ref/models/fields/#manytomanyfield
Such a through model always exists implicitly for any Django model by using the through option of ManyToManyField m2nfield and can be accessed via Model.m2nfield.through.objects.
Or you don't use a ManyToManyField at all and just create a separate model with two ForeignKeyFields to represent the pairs.
I have a ModelViewSet with an extra list_route to handle GET/POST for a certain list of objects:
class PickViewset(viewsets.ModelViewSet):
queryset = Pick.objects.all()
serializer_class = PickSerializer
def get_queryset(self):
#gets the correct queryset
#list_route(methods=['get', 'post'])
def update_picks(self, request, league, week, format = None):
if request.method == 'POST':
#process/save objects here
else:
#otherwise return the requested list
Thanks to the answer on my earlier question, this action can successfully handle a GET request as well as POST- however, when I try to POST more than one object, I get a JSON error:
"detail": "JSON parse error - Extra data: line 90 column 6 - line 181 column 2 (char 3683 - 7375)"
Where the specified location corresponds to the end of the first object. How can I change update_picks to handle a list of objects as well? Also, if this request may be a mix of new and updated existing objects, should I even be using POST for all, or just handle each POST/PUT on a per-object basis?
I considered adding a CreateModelMixin to the Viewset, however it can already create- but just one object. The ListCreateAPIView seems to be similar- it doesn't have an inherent list creation, but rather just the CreateModelMixin and ListModelMixin- both of which I think are provided by default when using a ModelViewset.
I think you have to overwrite the post method (see the question here Django Rest Framework Batch Create) and parse the json on your own using JSONParser().parse()
def post(self, request, *args, **kwargs):
if request.DATA['batch']:
json = request.DATA['batchData']
stream = StringIO(json)
data = JSONParser().parse(stream)
request._data = data
return super(CharacterDatumList, self).post(request, *args, **kwargs)
I'm trying to get an instance of a serializer in my overwritten list method and then pass it in through perform_create. Basically what this code does is it checks if the queryset is empty and if it is, we do a perform_create. The problem is that I'm trying to get an instance of the serializer so I can pass it in to the perform_create method. I don't believe the line serializer = self.get_serializer(data=request.data)
correctly grabs the serializer as it shows nothing when I try to log it. Any help is appreciated, thanks.
class ExampleViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwner)
def list(self, request):
queryset = self.get_queryset()
name = self.request.query_params.get('name', None)
# print(request.data)
if name is not None:
queryset = queryset.filter(name=name)
if (queryset.count() == 0):
serializer = self.get_serializer(data=request.data)
print(serializer)
return self.perform_create(serializer)
return HttpResponse(serializers.serialize('json', queryset))
elif name is None:
return HttpResponse(serializers.serialize('json', queryset))
As far as I can see, with
serializer = self.get_serializer(data=request.data)
you are trying to access POST data while responding to a GET request.
DRF ViewSets offer the methods:
list (called upon an HTTP GET request)
create (called upon an HTTP POST request)
retrieve (called upon an HTTP GET request)
update (called upon an HTTP PUT request)
partial_update (called upon an HTTP PATCH request)
destroy (called upon an HTTP DELETE request)
Also see this explicit example binding HTTP verbs to ViewSet methods
So if
you are POSTing data, the list method isn't called at all (as suggested by #Ivan in the very first comment you got above).
The solution is to move the code to the appropriate method, i.e create
Otherwise
your client is GETting, the list method is called, but request.data will be empty at best.
The solution is to make the client provide the parameters for the creation as GET parameters, along with name.
That way the view will find them in self.request.query_params
In case you have a form, simply change the way it sends its data by making it use HTTP GET. See here for further info
This question has been SOLVED by myself after better looking at it. Please read my answer below.
I am getting a "The fields option must be a list or tuple. Got str." when running my Django app.
Running exactly the same code with the debugger, and if I have a breakpoint in the line of the error, then it won't fail and what should be a tuple seems to be a tuple.
The problem seems to be located in the following code inside a DRF ModelSerializer:
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(ChHiveLevel1Serializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop fields that are specified in the `fields` argument.
for field_name in fields:
self.fields.pop(field_name)
print("fields to be included: ", self.fields)
In the views.py I just do:
...
hives = profile.hive_subscriptions
# En fields se le pasa el campo a eliminar del serializador
fields = ('priority', )
serializer = serializers.ChHiveLevel1Serializer(hives, fields=fields, many=True)
...
And this is the traceback:
Internal Server Error: /profiles/diegoocampo8/hives/
Traceback (most recent call last):
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/django/core/handlers/base.py", line 111, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/django/views/decorators/csrf.py", line 57, in wrapped_view
return view_func(*args, **kwargs)
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/django/views/generic/base.py", line 69, in view
return self.dispatch(request, *args, **kwargs)
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/rest_framework/views.py", line 452, in dispatch
response = self.handle_exception(exc)
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/rest_framework/views.py", line 449, in dispatch
response = handler(request, *args, **kwargs)
File "/home/diego/PycharmProjects/chattyhive/API/views.py", line 271, in get
serializer = serializers.ChHiveLevel1Serializer(hives, fields=fields, many=True)
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/rest_framework/serializers.py", line 96, in __new__
return cls.many_init(*args, **kwargs)
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/rest_framework/serializers.py", line 116, in many_init
child_serializer = cls(*args, **kwargs)
File "/home/diego/PycharmProjects/chattyhive/API/serializers.py", line 274, in __init__
print("fields to be included: ", self.fields)
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/rest_framework/utils/serializer_helpers.py", line 120, in __repr__
return dict.__repr__(self.fields)
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/rest_framework/serializers.py", line 611, in __repr__
return unicode_to_repr(representation.list_repr(self, indent=1))
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/rest_framework/utils/representation.py", line 97, in list_repr
if hasattr(child, 'fields'):
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/rest_framework/serializers.py", line 313, in fields
for key, value in self.get_fields().items():
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/rest_framework/serializers.py", line 837, in get_fields
field_names = self.get_field_names(declared_fields, info)
File "/home/diego/virtualenvs/chattyhive3.3.4/lib/python3.3/site-packages/rest_framework/serializers.py", line 889, in get_field_names
type(fields).__name__
TypeError: The `fields` option must be a list or tuple. Got str.
[05/May/2015 17:30:34] "GET /profiles/diegoocampo8/hives/ HTTP/1.1" 500 136024
If I remove the print("fields to be included: ", self.fields) then I got the same error but it will point to the line serializer = serializers.ChHiveLevel1Serializer(hives, fields=fields, many=True) in the views.py
Ok, I am still a noob in Python and I could be doing something very wrong, but what I can't understand is why if I insert a breakpoint in the print I've just mentioned, and I do the same api request with the debug on, then the code just works: I get my response just as I wanted, and it doesn't give any error (if I remove the breakpoint it will again give the error even if launching with the debugger).
Do you guys have any idea of what could be wrong? Thanks a lot in advance. Ask me for any extra info if you need it!
EDIT: Further explanations:
The whole serializer is this:
class ChHiveLevel1Serializer(serializers.ModelSerializer):
"""Used by the following API methods: GET hive list,
"""
category = serializers.SlugRelatedField(read_only=True, slug_field='code')
languages = serializers.SlugRelatedField(source='_languages', many=True, read_only=True, slug_field='language')
# If in the POST we only need to establish the relationship with User model (not update the model itself) we
# set read_only to True
creator = serializers.SlugRelatedField(read_only=True, slug_field='public_name')
tags = serializers.SlugRelatedField(many=True, read_only=True, slug_field='tag')
public_chat = ChPublicChatLevel1Serializer(many=False, read_only=True)
community_public_chats = ChCommunityPublicChatLevel1Serializer(many=True, read_only=True)
subscribed_users_count = serializers.IntegerField(source='get_subscribed_users_count', read_only=True)
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(ChHiveLevel1Serializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop fields that are specified in the `fields` argument.
for field_name in fields:
self.fields.pop(field_name)
print("fields to be included: ", self.fields)
class Meta:
model = ChHive
fields = ('name', 'slug', 'description', 'category', 'languages', 'creator', 'creation_date', 'tags',
'priority', 'type', 'public_chat', 'community_public_chats', 'subscribed_users_count')
I know its confusing to have 3 different 'fields' so I clarify this:
in the views I am passing a param 'fields' to the serializer, this is a tuple that contain names of fields to be dynamically removed from the serializer. Then inside the init I take pop out this param (so it is not sent to the superclass) and assign it the local tuple 'fields'. Finally, inside the 'if fields is not None' I am removing from self.fields (these are the fields defined in the serializer) the fields with name matching those inside the local tuple. I hope I could explain it better now.
Here is a video showing how when debugging and stopping at the breakpoint it just works: http://youtu.be/RImEMebBGLY
SOLUTION: First of all it seems to me that there is a bug with either pydevd or Django Rest Framework, that made my code work when in debugging mode and stoping in breakpoints (showed in the video). But I am not expert in Django / Python so it could be the expected behaviour.
As seen in the code above, the ChHiveLevel1Serializer serializer has nested serializers, being one of them, for example, ChPublicChatLevel1Serializer. Lets see how this serializer looks like:
class ChCommunityPublicChatListLevel1Serializer(serializers.ModelSerializer):
"""Used by the following API methods: GET hive list,
"""
chat = serializers.SlugRelatedField(read_only=True, slug_field='chat_id', allow_null=True)
class Meta:
model = ChCommunityPublicChat
fields = ('chat')
As the error said, fields is defined as a string instead of a tuple. The correct form would be:
class ChCommunityPublicChatListLevel1Serializer(serializers.ModelSerializer):
"""Used by the following API methods: GET hive list,
"""
chat = serializers.SlugRelatedField(read_only=True, slug_field='chat_id', allow_null=True)
class Meta:
model = ChCommunityPublicChat
fields = ('chat', )
While I agree that I made a mistake in how i defined this tuple, I still can't understand why with the debugger set to ON, it would just work. (like if using the debugger and stopping in breakpoints it suddenly interprets ('chat') as a tuple instead of a string).
I am a bit confused and I need some help.
I am displaying my objects using ModelFormset, then I am dynamically removing them using Ajax and then saving all of the objects again also using Ajax call. Everything is dynamic and the page is not reloaded at any time.
The problem is that when Django tries to save the whole formset using Ajax alfter an object or two has been deleted, it looks for the deleted object(s) and raises an IndexError: list index out of range, because the object(s) isn't at the queryset anymore.
This is how I am displaying and saving the formsets (simplified version - I think this is where the error comes from):
def App(request, slug):
TopicFormSet = modelformset_factory(Topic, form=TopicForm, extra=0, fields=('name',), can_delete=True)
SummaryFormSet = modelformset_factory(Summary, form=SummaryForm, extra=0, fields=('content',), can_delete=True)
tquery = user.topic_set.all().order_by('date')
squery = user.summary_set.all().order_by('date')
# saving formsets:
if request.method == 'POST' and request.is_ajax():
# the following two lines is where the error comes from:
t_formset = TopicFormSet(request.POST) # formset instance
s_formset = SummaryFormSet(request.POST) # formset instance
s_formset.save()
t_formset.save()
return render (blah...)
This is how I am removing objects (this is a different view):
def Remove_topic(request, slug, id):
topic = Topic.objects.get(pk=id)
summary = Summary.objects.get(topic = topic) # foreign key relatonship
topic.delete()
summary.delete()
# Ajax stuff....
if request.is_ajax():
return HttpResponse('blah..')
I have tried placing queryset = tquery and queryset = squery when instantiating t_formset and s_formset, but it didn't help. What should I do ? I am using Postgres db if that's useful.
The error:
> File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 115, in get_response
response = callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/auth/decorators.py", line 25, in _wrapped_view
return view_func(request, *args, **kwargs)
File "/home/eimantas/Desktop/Projects/Lynx/lynx/views.py", line 122, in App
t_formset = TopicFormSet(request.POST, queryset = tquery)
File "/usr/local/lib/python2.7/dist-packages/django/forms/models.py", line 441, in __init__
super(BaseModelFormSet, self).__init__(**defaults)
File "/usr/local/lib/python2.7/dist-packages/django/forms/formsets.py", line 56, in __init__
self._construct_forms()
File "/usr/local/lib/python2.7/dist-packages/django/forms/formsets.py", line 124, in _construct_forms
self.forms.append(self._construct_form(i))
File "/usr/local/lib/python2.7/dist-packages/django/forms/models.py", line 468, in _construct_form
kwargs['instance'] = self.get_queryset()[i]
File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 198, in __getitem__
return self._result_cache[k]
IndexError: list index out of range
This may be a case of a cascaded delete that is already deleting the summary object:
When an object referenced by a ForeignKey is deleted, Django by
default emulates the behavior of the SQL constraint ON DELETE CASCADE
and also deletes the object containing the ForeignKey.
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.on_delete
It has nothing to do with second view and Ajax calls. I think that You have messed up management form's fields. Like initial_form_count, total_form_count or something similar.
Another important point. Do not save formset before checking if it is valid:
t_formset = TopicFormSet(request.POST)
if t_formset.is_valid():
t_formset.save()
In G+ group I was adviced that technically it is possible to reset or "reload" the Queryset, but it would be very difficult to maintain "at all levels" and probably would give no benefit. I was adviced to use iteration and check if each object has been saved successfully when saving the formset forms (I would have to overwrite form = TopicForm's and form = SummaryForm's save() method.)
I decided not to use formsets at all, but to list and save each object individually, it will be better for me and my app's business logic.