Django Rest Framework can't identify serializer field name - django

This is the problem: I have a serializer field pointing to another serializer. I sending an allright request to server and I get this response:
{
"purchase_header": [
"This field is required."
]
}
But the field was sended by POST request (in debugger I can see it).
I finded this code in html.py (from DRF):
def parse_html_dict(dictionary, prefix=''):
"""
Used to support dictionary values in HTML forms.
{
'profile.username': 'example',
'profile.email': 'example#example.com',
}
-->
{
'profile': {
'username': 'example',
'email': 'example#example.com'
}
}
"""
ret = MultiValueDict()
regex = re.compile(r'^%s\.(.+)$' % re.escape(prefix))
for field in dictionary:
match = regex.match(field)
if not match:
continue
key = match.groups()[0]
value = dictionary.getlist(field)
ret.setlist(key, value)
return ret
Well, when debuging I can see that prefix variable contain "purchase_header" and field variable contain the same "purchase_header" string but de match fail returning None. Very rare... dictionary parameter contain all keys and values.
But If you can helpme I appreciate much.
This is the rest of significant code:
urls.py
router = DefaultRouter()
router.register(r'cancel-requests', PurchaseCancellationsRequestViewSet,
basename='purchase-cancel-requests')
router.register(r'invoices', PurchaseViewSet, basename='purchase')
urlpatterns = router.urls
urlpatterns += [path('payment-headers/', PaymentHeadersListAPIView.as_view(
), name='PaymentHeadersListAPIView')]
api.py
class PurchaseViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing purchases instances.
"""
serializer_class = PurchaseSerializer
queryset = Purchase.objects.all().order_by('-created_at')
permission_classes = (IsAuthenticated,)
serializer_action_classes = {
'list': PurchaseSerializer,
'create': PurchaseSerializer,
'retrieve': PurchaseSerializer,
'update': PurchaseSerializer,
'partial_update': PurchaseSerializer,
'destroy': PurchaseSerializer
}
def get_serializer_class(self):
try:
return self.serializer_action_classes[self.action]
except (KeyError, AttributeError):
return super().get_serializer_class()
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
kwargs["context"] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
def get_list_queryset(self):
try:
store = Store.objects.get(my_owner=self.request.user)
except EmptyResultSet as e:
print("Data Error: {0}".format(e))
urlquery = self.request.GET.get('cancelled', '')
cancelled = False
if urlquery == 'true':
cancelled = True
elif urlquery == '':
# return all
return Purchase.objects.filter(created_for=store)
return Purchase.objects.filter(cancelled=cancelled, created_for=store)
def put(self, request, *args, **kwargs):
if request.user.is_seller:
raise PermissionDenied(
detail='Only owners can cancellate Purchases')
# seller create invoice cancellation request
return self.update(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
request.data['created_by'] = request.user.pk
print(request.data)
if request.user.is_owner is True:
try:
request.data['created_for'] = Store.objects.get(
my_owner=request.user).pk
except Exception as e:
print(e)
return Response(data='User has no Store associated',
status=status.HTTP_400_BAD_REQUEST)
else:
try:
request.data['created_for'] = request.user.seller_profile \
.my_store.pk
except Exception as e:
print(e)
return Response(data='User has no Store associated',
status=status.HTTP_400_BAD_REQUEST)
return self.create(request, *args, **kwargs)
def list(self, request, *args, **kwargs):
# TODO: date ranges
if self.request.user.is_seller:
# TODO: Change -> Indeed the seller can get his own created
# purchases and refactor this to method
raise PermissionDenied(
detail='Only owners can get purchase list.')
queryset = self.get_list_queryset()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
serializers.py
from rest_framework import serializers
from .models import Purchase, PurchaseCancellationRequest, PurchaseHeader
class PurchaseHeaderSerializer(serializers.ModelSerializer):
class Meta:
model = PurchaseHeader
fields = ['id', 'name']
extra = {
'id': {'read_only': True},
'name': {'read_only': True}
}
class PurchaseSerializer(serializers.ModelSerializer):
purchase_header = PurchaseHeaderSerializer()
class Meta:
model = Purchase
fields = ['id', 'cancelled', 'created_at', 'created_by',
'created_for', 'pic', 'total_amount', 'purchase_header']
extra = {
'cancelled': {'read_only': True},
'created_at': {'read_only': True},
'id': {'read_only': True}
}
def validate_total_amount(self, value):
"""
Sanitize the amount value.
:return: total_amount as float
"""
total_amount = None
try:
total_amount = float(value)
except Exception as e:
print(e)
if total_amount:
return value
else:
raise serializers.ValidationError('Exists an error with '
'total_amount value, probably '
'an empty param or bad float '
'format')
def validate_cancelled(self, value):
"""
Sanitize the cancelled value.
:return: cancelled as python boolean
"""
cancelled = None
try:
cancelled = False if value == 'false' else True
except Exception as e:
print(e)
if cancelled:
return value
else:
raise serializers.ValidationError('Exists an error with '
'cancelled value, probably an '
'empty param or bad boolean '
'value')
def create(self, validated_data):
payment_header_pk = validated_data.pop('purchase_header')
payment_header_obj = PaymentHeader.objects.get(pk=payment_header_pk)
purchase_obj = Purchase.objects.create(
payment_header=payment_header_obj, **validated_data)
return purchase_obj
class PurchaseCancellationRequestSerializer(serializers.ModelSerializer):
class Meta:
model = PurchaseCancellationRequest
fields = ['purchase']
models.py
class PurchaseHeader(SoftDeleteModel):
name = models.CharField(max_length=50)
city = models.ManyToManyField(City, through='purchases.ProviderCity')
created_at = models.DateTimeField(default=timezone.now(), editable=False)
modified_at = models.DateTimeField(default=timezone.now())
class ProviderCity(SoftDeleteModel):
city = models.ForeignKey(City, on_delete=models.PROTECT)
purchase_header = models.ForeignKey(PurchaseHeader,
on_delete=models.PROTECT)
class Meta:
unique_together = ('city', 'purchase_header')
Thank for all friends!!

Related

How do I fix 'Message' instance needs to have a primary key value before this relationship can be used

When I try to add a Message from the admin pages I get the 'Message' instance needs to have a primary key value before this relationship can be used.
Here is my models.py Message class...
class Message(models.Model):
""" Message as sent through a Submission. """
default_id = time.time()
#adding primary key to satisfy error
id = models.BigAutoField(primary_key=True, auto_created=True)
title = models.CharField(max_length=200, verbose_name=_('title'))
slug = models.SlugField(verbose_name=_('slug'))
newsletter = models.ForeignKey(
Newsletter, verbose_name=_('newsletter'), on_delete=models.CASCADE, default=get_default_newsletter
)
date_create = models.DateTimeField(
verbose_name=_('created'), auto_now_add=True, editable=False
)
date_modify = models.DateTimeField(
verbose_name=_('modified'), auto_now=True, editable=False
)
class Meta:
verbose_name = _('message')
verbose_name_plural = _('messages')
unique_together = ('slug', 'newsletter')
#order_with_respect_to = 'newsletter'
def __str__(self):
try:
return _("%(title)s in %(newsletter)s") % {
'title': self.title,
'newsletter': self.newsletter
}
except Newsletter.DoesNotExist:
logger.warning('No newsletter has been set for this message yet.')
return self.title
def get_next_article_sortorder(self):
""" Get next available sortorder for Article. """
next_order = self.articles.aggregate(
models.Max('sortorder')
)['sortorder__max']
if next_order:
return next_order + 10
else:
return 10
#cached_property
def _templates(self):
"""Return a (subject_template, text_template, html_template) tuple."""
return self.newsletter.get_templates('message')
#property
def subject_template(self):
return self._templates[0]
#property
def text_template(self):
return self._templates[1]
#property
def html_template(self):
return self._templates[2]
#classmethod
def get_default(cls):
try:
return cls.objects.order_by('-date_create').all()[0]
except IndexError:
return None
and the admin.py module
class MessageAdmin(NewsletterAdminLinkMixin, ExtendibleModelAdminMixin,
admin.ModelAdmin):
logger.critical('MessageAdmin')
save_as = True
list_display = (
'admin_title', 'admin_newsletter', 'admin_preview', 'date_create',
'date_modify'
)
list_filter = ('newsletter', )
date_hierarchy = 'date_create'
prepopulated_fields = {'slug': ('title',)}
inlines = [ArticleInline, AttachmentInline, ]
""" List extensions """
def admin_title(self, obj):
return format_html('{}', obj.id, obj.title)
admin_title.short_description = _('message')
def admin_preview(self, obj):
url = reverse('admin:' + self._view_name('preview'), args=(obj.id,),
current_app=self.admin_site.name)
return format_html('{}', url, _("Preview"))
admin_preview.short_description = ''
""" Views """
def preview(self, request, object_id):
logger.critical('preview object_id = %s', object_id)
return render(
request,
"admin/newsletter/message/preview.html",
{'message': self._getobj(request, object_id),
'attachments': Attachment.objects.filter(message_id=object_id)},
)
#xframe_options_sameorigin
def preview_html(self, request, object_id):
logger.critical('preview_html object_id = %s', object_id)
message = self._getobj(request, object_id)
if not message.html_template:
raise Http404(_(
'No HTML template associated with the newsletter this '
'message belongs to.'
))
c = {
'message': message,
'site': Site.objects.get_current(),
'newsletter': message.newsletter,
'date': now(),
'STATIC_URL': settings.STATIC_URL,
'MEDIA_URL': settings.MEDIA_URL
}
return HttpResponse(message.html_template.render(c))
#xframe_options_sameorigin
def preview_text(self, request, object_id):
message = self._getobj(request, object_id)
c = {
'message': message,
'site': Site.objects.get_current(),
'newsletter': message.newsletter,
'date': now(),
'STATIC_URL': settings.STATIC_URL,
'MEDIA_URL': settings.MEDIA_URL
}
return HttpResponse(
message.text_template.render(c),
content_type='text/plain'
)
def submit(self, request, object_id):
submission = Submission.from_message(self._getobj(request, object_id))
change_url = reverse(
'admin:newsletter_submission_change', args=[submission.id])
return HttpResponseRedirect(change_url)
def subscribers_json(self, request, object_id):
message = self._getobj(request, object_id)
json = serializers.serialize(
"json", message.newsletter.get_subscriptions(), fields=()
)
return HttpResponse(json, content_type='application/json')
""" URLs """
def get_urls(self):
urls = super().get_urls()
my_urls = [
path('<object_id>/preview/',
self._wrap(self.preview),
name=self._view_name('preview')),
path('<object_id>/preview/html/',
self._wrap(self.preview_html),
name=self._view_name('preview_html')),
path('<object_id>/preview/text/',
self._wrap(self.preview_text),
name=self._view_name('preview_text')),
path('<object_id>/submit/',
self._wrap(self.submit),
name=self._view_name('submit')),
path('<object_id>/subscribers/json/',
self._wrap(self.subscribers_json),
name=self._view_name('subscribers_json')),
]
return my_urls + urls
I have tried a number of the fixes recommended in posts on this error here in stackoverflow and on github. but none have solved the problem.
I created the id = models.BigAutoField(primary_key=True, auto_created=True) and varified in the sqlite db that the primary key and auto increment are in the structure.
I feel like creating a through or xref table between Message and Article would solve it, but I hesitate because the developers of Jazzband Newsletter (https://github.com/jazzband/django-newsletter) did it this way for a reason and that Django or Python updates have let this creep in.

Drf throttling restart when POST

I created some app and want to set up different throttling for GET/POST,PUT,PATCH. When I make a GET request, it works fine. But when I perfom POST/PUT/PATCH it looks like cache is restarted. I want to create also extra headers so that's why I think this is actually the problem.
Throttling.py
class SetHeaders:
def throttle_success(self):
self.history.insert(0, self.now)
self.cache.set(self.key, self.history, self.duration)
print(self.key, self.history, self.duration)
print(f"REMAIN TIME {self.duration - (self.now - self.history[-1])}")
print(f'LIMIT {self.get_rate()}')
print(f'REMAIN RATE {self.num_requests - len(self.history) + 1}')
return True
class UserPostRateThrottle(SetHeaders, throttling.UserRateThrottle):
scope = "user_post"
def allow_request(self, request, view):
if request.method == "GET":
return True
return super().allow_request(request, view)
class UserGetRateThrottle(SetHeaders, throttling.UserRateThrottle):
scope = "user_get"
def allow_request(self, request, view):
if request.method in ("POST", "PUT", "PATCH"):
return True
return super().allow_request(request, view)
views.py
class TruckViewSet(viewsets.ModelViewSet):
queryset = Truck.confirmed.all()
serializer_class = TruckSerializer
filterset_class = TruckFilter
permission_classes = (
permissions.DjangoModelPermissions,
IsOwnerOrReadOnly,
)
throttle_classes = (UserGetRateThrottle, UserPostRateThrottle)
def _get_images(self, request):
for img in request.data.getlist("image"):
image_serializer = TruckImageSerializer(data={"image": img})
image_serializer.is_valid(raise_exception=True)
def create(self, request, *args, **kwargs):
if request.data.get("image"):
self._get_images(request)
return super(TruckViewSet, self).create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def update(self, request, *args, **kwargs):
if request.data.get("image"):
self._get_images(request)
return super(TruckViewSet, self).update(request, *args, **kwargs)
serializers.py
class TruckSerializer(serializers.ModelSerializer):
location = LocationSerializer(
read_only=True,
)
owner = serializers.PrimaryKeyRelatedField(
read_only=True,
)
name = serializers.CharField(
max_length=50,
validators=[
validators.UniqueValidator(queryset=Truck.confirmed.all())
],
)
images = serializers.SerializerMethodField()
payment_methods = serializers.SerializerMethodField()
class Meta:
model = Truck
fields = (
"id",
"owner",
"name",
"phone",
"email",
"facebook",
"instagram",
"page_url",
"description",
"city",
"payment_methods",
"images",
"updated",
"location",
)
def _get_payments(self, data):
new_payments = []
payments = data.get("payment_methods")
for payment in payments.split(", "):
try:
filtered_payment = PaymentMethod.objects.get(
payment_name__iexact=payment
).id
except PaymentMethod.DoesNotExist:
raise serializers.ValidationError(
"Given payment method does not match"
)
new_payments.append(filtered_payment)
return new_payments
def create(self, validated_data):
data = self.context.get("view").request.data
new_payments = data.get("payment_methods", {})
if new_payments:
new_payments = self._get_payments(data)
truck = Truck.objects.create(**validated_data)
if data.get("image"):
for image_data in data.getlist("image"):
TruckImage.objects.create(truck=truck, image=image_data)
truck.payment_methods.add(*new_payments)
return truck
def update(self, instance, validated_data):
data = self.context.get("view").request.data
method = self.context.get("view").request.method
new_payments = data.get("payment_methods", {})
if new_payments:
new_payments = self._get_payments(data)
if data.get("image"):
images = instance.images.all()
if images.exists():
instance.images.all().delete()
for image_data in data.getlist("image"):
TruckImage.objects.create(truck=instance, image=image_data)
if method == "PUT" or method == "PATCH" and new_payments:
instance.payment_methods.clear()
instance.payment_methods.add(*new_payments)
return super(TruckSerializer, self).update(instance, validated_data)
def get_images(self, obj):
images = obj.images.all()
return [img.image.url for img in images]
def get_payment_methods(self, obj):
return obj.payment_methods.values_list("payment_name", flat=True)
settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": [
'rest_framework.throttling.ScopedRateThrottle',
# 'rest_framework.throttling.UserRateThrottle'
],
"DEFAULT_THROTTLE_RATES": {
"user_get": "6/day",
"user_post": "4/day",
# 'user': '5/day'
},
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
"PAGE_SIZE": 10,
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend"
],
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning",
}
So when GET requested is performed I get:
>>>REMAIN TIME 86399.02145123482
>>>LIMIT 6/day
>>>REMAIN RATE 3
and so on...
But in case of other request i get the same info all the time
>>>REMAIN TIME 86400.0
>>>LIMIT 4/day
>>>REMAIN RATE 4
What i've tried?
I've tried to change to simple UserRateThrottle, but nothing changed. So I think custom throttle is made correctly. Also I was changing between default Django cache/Redis server/Dummy cache and still not working as desired.
Thanks in advance!

DRF local variable 'msg_data' referenced before assignment

This is my serializers.py
class UserProfileSerializer(serializers.ModelSerializer):
img_count = serializers.SerializerMethodField('get_img_count')
post_count = serializers.SerializerMethodField('get_post_count')
msg_count = serializers.SerializerMethodField('get_msg_count')
class Meta:
model = User
fields = ('id', 'username', 'img_count', 'post_count', 'msg_count')
def get_img_count(self, obj):
try:
img_data = ImgSerializer(Img.objects.filter(author=obj.id), many=True)
except img_data.DoesNotExist:
return 0
return img_data
def get_post_count(self, obj):
try:
post_data = PostSerializer(Post.objects.filter(author=obj.id), many=True)
except post_data.DoesNotExist:
return 0
return post_data
def get_msg_count(self, obj):
try:
msg_data = Smessage(Msg.objects.filter(author=obj.id), many=True)
except msg_data.DoesNotExist:
return 0
return msg_data
This is my views.py
class UserProfile(APIView):
permission_classes = [AllowAny]
def get(self, request):
query = User.objects.all()
serializer = UserProfileSerializer(query, many=True)
return Response(serializer.data)
This is the Error Snippet
I want to get this
{
"id": 9,
"username": "emil#gmail.com",
"img_count:3,
"post_count":5,
"msg_count":50,
}
also getting error after using img_count.count().
You should change the exceptions to:
img_data.DoesNotExist --> Img.DoesNotExist
post_data.DoesNotExist --> Post.DoesNotExist
msg_data.DoesNotExist --> Msg.DoesNotExist
Because, instances does not have any exception object, rather the model classes has them. More information can be found in documentation.
Update
If you just want the count, then you don't need to use such extensive implementation. You can simply try:
def get_img_count(self, obj):
return Img.objects.filter(author=obj.id).count()
def get_post_count(self, obj):
return Post.objects.filter(author=obj.id).count()
def get_msg_count(self, obj):
return Msg.objects.filter(author=obj.id).count()
You should set default value of each variable because in case of error other than DoesNotExist, you will face this error.
class UserProfileSerializer(serializers.ModelSerializer):
img_count = serializers.SerializerMethodField('get_img_count')
post_count = serializers.SerializerMethodField('get_post_count')
msg_count = serializers.SerializerMethodField('get_msg_count')
class Meta:
model = User
fields = ('id', 'username', 'img_count', 'post_count', 'msg_count')
def get_img_count(self, obj):
img_data = 0
try:
img_data = ImgSerializer(Img.objects.filter(author=obj.id), many=True).data
except Img.DoesNotExist:
pass
return img_data
def get_post_count(self, obj):
post_data = 0
try:
post_data = PostSerializer(Post.objects.filter(author=obj.id), many=True).data
except Post.DoesNotExist:
pass
return post_data
def get_msg_count(self, obj):
msg_data = 0
try:
msg_data = Smessage(Msg.objects.filter(author=obj.id), many=True).data
except Msg.DoesNotExist:
pass
return msg_data

Django rest framework custom return response

So I have this custom register API which registers a user, but when user successfully register, I want it to have this message "You have successfully register an account!" But I tried a different method but get an error instead.
serializer.py
class UserCreate2Serializer(ModelSerializer):
email = EmailField(label='Email Address')
valid_time_formats = ['%H:%M', '%I:%M%p', '%I:%M %p']
birthTime = serializers.TimeField(format='%I:%M %p', input_formats=valid_time_formats, allow_null=True, required=False)
class Meta:
model = MyUser
fields = ['username', 'password', 'email', 'first_name', 'last_name', 'gender', 'nric', 'birthday', 'birthTime']
extra_kwargs = {"password": {"write_only": True}}
def validate(self, data): # to validate if the user have been used
email = data['email']
user_queryset = MyUser.objects.filter(email=email)
if user_queryset.exists():
raise ValidationError("This user has already registered.")
return data
def create(self, validated_data):
username = validated_data['username']
password = validated_data['password']
email = validated_data['email']
first_name = validated_data['first_name']
last_name = validated_data['last_name']
gender = validated_data['gender']
nric = validated_data['nric']
birthday = validated_data['birthday']
birthTime = validated_data['birthTime']
user_obj = MyUser(
username = username,
email = email,
first_name = first_name,
last_name = last_name,
gender = gender,
nric = nric,
birthday = birthday,
birthTime = birthTime,
)
user_obj.set_password(password)
user_obj.save()
return validated
views.py
class CreateUser2View(CreateAPIView):
permission_classes = [AllowAny]
serializer_class = UserCreate2Serializer
queryset = MyUser.objects.all()
I tried changing this into the serializer
user_obj.set_password(password)
user_obj.save()
content = {'Message': 'You have successfully register an account'}
return content
But got an error instead. I'm unsure how to do the custom response as I only know it is to be done on views.py.
But if I do this on view:
class CreateUser2View(CreateAPIView):
permission_classes = [AllowAny]
serializer_class = UserCreate2Serializer
queryset = MyUser.objects.all()
def post(self, request):
content = {'Message': 'You have successfully register'}
return Response(content, status=status.HTTP_200_OK)
It will show this even if the validation is incorrect. Please help me as I'm still inexperienced in DRF.
class CreateUser2View(CreateAPIView):
permission_classes = [AllowAny]
serializer_class = UserCreate2Serializer
queryset = MyUser.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response({'Message': 'You have successfully register'}, status=status.HTTP_201_CREATED, headers=headers)
from rest_framework import status
from rest_framework.views import exception_handler as base_handler
def exception_handler(exception, context):
"""
Django rest framework for custom exception handler
#exception : Exception
#context : Context
"""
response = base_handler(exception, context)
if response is not None:
response = custom_response(response)
return response
def serializer_errors(data):
"""
Django rest framework serializing the errors
#data : data is python error dictionary
"""
errors = {}
got_msg = False
message = "Bad request."
if isinstance(data, dict):
for key, value in data.items():
try:
if isinstance(value, list):
value = ", ".join(value)
except Exception:
pass
if not got_msg:
if value:
message = value
got_msg = True
errors[key] = value
if not isinstance(message, str):
message = "Bad request"
return errors, message
def error(source, detail, code):
"""
Create python dictionary of error
#source : Where coming the error
#detail : Error detail information
"""
error = {}
error["source"] = source
error["detail"] = detail
if code:
error["code"] = code
return error
def custom_response(response):
"""
Modification the response of django rest framework
#response : Return response
"""
modified_data = {}
modified_data["code"] = response.status_code
modified_data["status"] = get_status(response.status_code)
data, message = serializer_errors(response.data)
modified_data["message"] = message
modified_data["errors"] = data
response.data = modified_data
return response
def get_status(status_code):
"""
Return result base on return http status
#status_code : HTTP status code
"""
result = ""
if status_code == status.HTTP_200_OK:
result = "Success"
elif status_code == status.HTTP_201_CREATED:
result = "Instance create"
elif status_code == status.HTTP_204_NO_CONTENT:
result = "Instance deleted"
elif status_code == status.HTTP_403_FORBIDDEN:
result = "Forbidden error"
elif status_code == status.HTTP_404_NOT_FOUND:
result = "Instance not found"
elif status_code == status.HTTP_400_BAD_REQUEST:
result = "Bad request"
elif status_code == status.HTTP_401_UNAUTHORIZED:
result = "Unauthorized request"
elif status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
result = "Internal server error"
else:
result = "Unknown error"
return result

Two model forms in one view/template in Django using CBV

Can someone show the example of usage of two model forms in one view/template? The task is to process more than one form in one CBV, where model of the second form has FK to first form model, so value from select widget from first form must be processed as value for object, created in second form.
My forms looks like this:
class CompanyEditForm(ModelForm):
name = CharField(label="Наименование", required=True)
type = ModelChoiceField(queryset=CompanyTypes.objects.all(), label="Тип компании", empty_label=None, initial=3)
description = CharField(label="Описание компании", required=False, widget=forms.Textarea(attrs={'cols': 40, 'rows':3}))
www = CharField(label="WEB сайт", required=False)
class Meta:
model = Companies
fields = ('type', 'name', 'description', 'www')
class BranchEditForm(ModelForm):
name = CharField(label="Наименование офиса", required=True)
type = ModelChoiceField(queryset=BranchTypes.objects.all(), label="Тип отделения", empty_label=None, initial=1)
class Meta:
model = Branches
exclude = ('company', 'address')
class AddressEditForm(ModelForm):
postalcode = IntegerField(label="Почтовый код", required=False)
city = CharField(label="Город", required=True)
street = CharField(label="Улица", required=True)
app = CharField(label="Дом", required=True)
app_extra = CharField(label="Корпус / Строение", required=False)
comment = CharField(label="Примечание к адресу", required=False)
exclude = ('company',)
class Meta:
model = Addresses
fields = ('postalcode', 'city', 'street', 'app', 'app_extra', 'comment')
UPDATE
I wrote this mixin:
class MultiFormCreate(FormMixin, TemplateResponseMixin, View):
formconf = None
def get_form_classes(self):
form_classes = {}
for key, params in self.formconf.items():
form_classes[key] = params.formclass
return self.form_classes
def get_initial(self, classname):
inicial = {}
if 'inicial' in self.formconf[classname]:
inicial = self.formconf[classname]['inicial'].copy()
return inicial
def get_form_kwargs(self, classname):
kwargs = {'initial': self.get_initial(classname), 'prefix': classname}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def get_forms(self):
for classname, params in self.formconf.items():
log.info("Name: %s, Params: %s" % (classname, params))
return dict(
[(classname, params['formclass'](**self.get_form_kwargs(classname))) for classname, params in self.formconf.items()])
def get(self, request, *args, **kwargs):
forms = self.get_forms()
return self.render_to_response(self.get_context_data(forms=forms))
def get_success_url(self):
if self.success_url:
url = force_text(self.success_url)
else:
raise ImproperlyConfigured(
"No URL to redirect to. Provide a success_url.")
return url
then in a view i only need to write processing in post:
class CompanyCreate(MultiFormCreate):
template_name = 'company/edit.html'
success_url = '/forbidden/'
formconf = {
'company': {'formclass': CompanyEditForm, 'inicial': {'name': "TESTNAME"}},
'branch': {'formclass': BranchEditForm},
'address': {'formclass': AddressEditForm}
}
def post(self, request, *args, **kwargs):
forms = self.get_forms()
cform = forms['company']
aform = forms['address']
bform = forms['branch']
if cform.is_valid() and aform.is_valid() and bform.is_valid():
''' Creating main form form object (by saving tthe form) '''
company_object = cform.save()
''' Creating dependant object '''
address_object = aform.save(commit=False)
branch_object = bform.save(commit=False)
''' assigning dependent fields '''
address_object.company = company_object
''' saving dependent _object_ '''
address_object.save()
''' managing another dependent fields '''
branch_object.company = company_object
branch_object.address = address_object
''' saving last object '''
branch_object.save()
return HttpResponseRedirect(self.get_success_url())
else:
forms = self.get_forms()
return self.render_to_response(self.get_context_data(forms=forms))
Code for view file :
if request.method == "POST":
company_form = CompanyEditForm(request.POST)
if company_form.is_valid():
company_object = company_form.save()
post_data = request.POST.copy()
branch_form = BranchEditForm(post_data)
branch_form.data['company'] = company_object
if branch_form.is_valid():
branch_object.save()
rest implement your business logic ..