Updating a field within another Model's save method with F() - python

I'm attempting to create a voting system where the score of a given post is separate from the type of votes placed by users. In the event that a user deletes a profile, a given score should not increment/decrement due to their vote being deleted. Therefore scores are only updated by using an F('score') + 1 or F('score') - 1 expression.
Within Vote.save(), I'm trying to implement this, yet the Question.score field doesn't update when the Vote is created. How can I get the test to pass where the score in the question goes from 0 to 1? django.db.models.F is in fact being imported into the module but it's not displayed here.
class TestQuestionScoreUpVote(TestCase):
'''Verify that a Question's score increments by one point
when the Vote is an "up" vote.'''
#classmethod
def setUpTestData(cls):
tag1 = Tag.objects.create(name="Tag1")
user = get_user_model().objects.create_user("TestUser")
profile = Profile.objects.create(user=user)
cls.question = Question.objects.create(
title="Question__001",
body="Content of Question 001",
profile=profile
)
cls.question.tags.add(tag1)
user_vote = Vote.objects.create(
profile=profile, type="upvote", content_object=cls.question
)
def test_new_user_vote_on_question(self):
self.assertEqual(self.question.score, 1)
class Post(Model):
body = TextField()
date = DateField(default=date.today)
comment = ForeignKey('Comment', on_delete=CASCADE, null=True)
profile = ForeignKey(
'authors.Profile', on_delete=SET_NULL, null=True,
related_name='%(class)ss',
related_query_name="%(class)s"
)
vote = GenericRelation(
'Vote', related_query_name="%(class)s"
)
score = IntegerField(default=0)
class Meta:
abstract = True
class Question(Post):
title = CharField(max_length=75)
tags = ManyToManyField(
'Tag', related_name="questions", related_query_name="question"
)
views = IntegerField(default=0)
objects = Manager()
postings = QuestionSearchManager()
class Meta:
db_table = "question"
ordering = ["-score" , "-date"]
def __repr__(self):
return f"{self.__class__.__name__}(title={self.title})"
class Vote(Model):
profile = ForeignKey(
'authors.Profile', on_delete=SET_NULL, null=True,
related_name="votes"
)
type = CharField(max_length=7)
content_type = ForeignKey(ContentType, on_delete=CASCADE)
object_id = PositiveIntegerField()
content_object = GenericForeignKey()
def save(self, *args, **kwargs):
post = ContentType.objects.get_for_id(
self.content_type_id
).get_object_for_this_type(id=self.object_id)
if self.type == "upvote":
post.score = F("score") + 1
else:
post.score = F("score") - 1
post.refresh_from_db()
super().save(*args, **kwargs)

You are forgetting to call post.save() after the object change:
def save(self, *args, **kwargs):
post = ContentType.objects.get_for_id(
self.content_type_id
).get_object_for_this_type(id=self.object_id)
if self.type == "upvote":
post.score = F("score") + 1
else:
post.score = F("score") - 1
post.save() # <- HERE
post.refresh_from_db()
super().save(*args, **kwargs)

Related

How to coordinate randomly chosen objects in a DRF APIView?

I am building an API for a game and need to select a random game round for a randomly chosen resource.
Choosing the random resource works. What I am trying to do now, in order to coordinate players is to filter the game rounds by the resource that has been chosen randomly and then return a random game round.
The code below shows what I have tried so far, namely to access the resource for which a game round has been played over the method with the #property decorator.
models.py
class Resource(models.Model):
id = models.PositiveIntegerField(null=False, primary_key=True)
hash_id = models.CharField(max_length=256)
creators = models.ManyToManyField(Creator)
titles = models.ManyToManyField(Title)
created_start = models.DateField(null=True)
created_end = models.DateField(null=True)
location = models.CharField(max_length=512, null=True)
institution_source = models.CharField(max_length=512, blank=True)
institution = models.CharField(max_length=512, blank=True)
origin = models.URLField(max_length=256, null=True)
enabled = models.BooleanField(default=True)
media_type = models.CharField(max_length=256, default='picture')
objects = models.Manager()
def __str__(self):
return self.hash_id or ''
#property
def tags(self):
tags = self.taggings.values('tag').annotate(count=Count('tag'))
return tags.values('tag_id', 'tag__name', 'tag__language', 'count')
class Gameround(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gamesession = models.ForeignKey(Gamesession, on_delete=models.CASCADE)
created = models.DateTimeField(editable=False)
score = models.PositiveIntegerField(default=0)
objects = models.Manager()
def save(self, *args, **kwargs):
if not self.id:
self.created = timezone.now()
return super().save(*args, **kwargs)
def create(self):
pass
#property
def tags(self):
tags = self.taggings.values('tag')
return tags.values('tag_id', 'tag__name', 'tag__language', 'resource_id')
class Tagging(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gameround = models.ForeignKey(Gameround, on_delete=models.CASCADE, related_name='taggings')
resource = models.ForeignKey(Resource, on_delete=models.CASCADE, related_name='taggings')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
created = models.DateTimeField(editable=False)
score = models.PositiveIntegerField(default=0)
origin = models.URLField(max_length=256, blank=True, default='')
objects = models.Manager()
def create(self, tag):
tagging = self.create(tag=tag)
def __str__(self):
return str(self.tag) or ''
def save(self, *args, **kwargs):
if not self.id:
self.created = timezone.now()
return super().save(*args, **kwargs)
I am then serialising the game round like this:
*serializers.py
class GameroundSerializer(serializers.ModelSerializer):
gamesession = GamesessionSerializer(read_only=True)
user = CustomUserSerializer(read_only=True)
tags_to_compare = serializers.SerializerMethodField('get_tags_to_compare')
class Meta:
model = Gameround
fields = ['id', 'user', 'gamesession', 'created', 'score', 'tags_to_compare']
def get_tags_to_compare(self, round):
taggings = round.tags
return taggings
def to_representation(self, data):
data = super().to_representation(data)
return data
In the view I have a get request which first chooses a random resource, then I am trying to filter the game rounds to only include the ones where this resource was involved and then I am trying to retrieve a random game round.
views.py
class GameroundView(APIView):
"""
API endpoint for game rounds
"""
def get(self, request, *args, **kwargs):
controller = GameViewController()
random_resource = controller.get_resource()
gameround = Gameround.objects.all().filter(taggings__resource_id=random_resource).order_by("?").first()
gameround_serializer = GameroundSerializer(gameround)
return Response(gameround_serializer.data)
This does not seem to be working since I keep getting the error "The response content must be rendered before it can be iterated over."
I have also tried creating an id variable and passing it on as the resource_id like this:
# id of the random Resource for the game round
random_resource_id = random_resource.get(random_resource.id)
gameround = Gameround.objects.all().filter(taggings__resource_id=random_resource_id).order_by("?").first()
This leads to the 'Response' object has no attribute 'id' error when I reload the page in the browser.
Does anyone know how I can solve this issue?
Thank you in advance!
Below also the Gameviewcontroller that is inside the views.py file
class GameViewController:
def get_random_object(self, MyModel):
random_object = None
object_count = MyModel.objects.all().count() + 1
while not MyModel.objects.all().filter(id=random_object).exists():
for obj in range(object_count):
n = random.randint(1, object_count)
if MyModel.objects.all().filter(id=n).exists():
random_object = n
return random_object
def get_random_id(self, MyModel):
"""Method for large querysets"""
random_object = None
object_count = MyModel.objects.all().count() + 1
for obj in range(object_count):
n = random.randint(1, object_count)
while not MyModel.objects.all().filter(id=n).exists():
alt = random.randint(1, object_count)
if MyModel.objects.all().filter(id=alt).exists():
random_object = n
return random_object
def get_resource(self):
random_resource_id = self.get_random_object(Resource)
current_resource = Resource.objects.all().filter(id=random_resource_id)
resource_serializer = ResourceSerializer(current_resource, many=True)
data = {'resource': resource_serializer.data}
return Response(data, status=status.HTTP_200_OK)
def get_gameround_matching_resource(self, random_resource):
"""Checks if a previously played game round exists for a randomly chosen resource"""
current_gameround = Gameround.objects.all().filter(ressource__in=random_resource).order_by('?').first()
if current_gameround.exists():
gameround_serializer = GameroundSerializer(current_gameround)
data = {'gameround': gameround_serializer.data}
return Response(data, status=status.HTTP_200_OK)
def timer(self, start):
"""Start a new timer as soon as a gameround has been selected"""
start_time = start
elapsed_time = None
if start_time is not None:
start_time = time.perf_counter()
"""Stop the timer, and return the elapsed time"""
if start_time is None:
elapsed_time = time.perf_counter() - start_time
return elapsed_time
def get_gamesession(self):
"""Retrieves a randomly chosen gamesession object"""
current_gamesession = Gamesession.objects.all().order_by('?').first()
gamesession_serializer = GamesessionSerializer(current_gamesession)
data = {'gameround': gamesession_serializer.data}
return Response(data, status=status.HTTP_200_OK)
I finally managed to solve it by rewriting the get request like this:
def get(self, request, *args, **kwargs):
random_resource = Resource.objects.all().order_by('?').first()
resource_serializer = ResourceSerializer(random_resource) # Response is a serialized JSON object
random_resource_id = random_resource.id # id of the random Resource for the game round
gameround = Gameround.objects.all().filter(taggings__resource_id=random_resource_id).order_by("?").first()
gameround_serializer = GameroundSerializer(gameround)
return Response({
'resource to coordinate': resource_serializer.data,
'gameround': gameround_serializer.data,
})

Django UpdateView not populating form

I've been stuck on this problem for hours, looking through Django documentation and adjusting my code hasn't helped.
I have manually coded a HTML form (for styling reasons). Everything works fine until I try to edit an object via this form.
I'm using a standard UpdateView with no additions but for some reason the form is not populating the data in the object.
class RPLogbookEditEntry(LoginRequiredMixin, UpdateView):
login_url = 'ihq-login'
template_name = 'internethq/ihq-rplogbookform.html'
model = RPLogbookEntry
fields = ['entry_date', 'rpa_code', 'rpa_icao_code','rpa_registration', 'user_role', 'crew', 'crew_role', 'launch_location', 'recovery_location',
'remarks', 'vlos', 'evlos1', 'evlos2', 'bvlos', 'ft_day', 'ft_night', 'remarks', 'no_of_landings_day', 'no_of_landings_night']
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.rpa = RPA.objects.get(rpa_code=form.cleaned_data['rpa_code'])
return super().form_valid(form)
def form_invalid(self, form):
return super().form_invalid(form)
def get_success_url(self):
return reverse('ihq-rplogbook-list') + '?month=' + str(self.object.entry_date.month) + '&year=' + str(self.object.entry_date.year)
models.py
class RPLogbookEntry(models.Model):
## Date
entry_date = models.DateField()
## RPA Details
rpa_obj = RPA.objects.only('rpa_code')
RPA_CODE_CHOICES = []
for vars in rpa_obj:
RPA_CODE_CHOICES.append((vars.rpa_code, vars.rpa_code))
rpa_code = models.CharField(choices=RPA_CODE_CHOICES, max_length=20)
rpa_icao_code = models.CharField(max_length=15)
rpa_registration = models.CharField(max_length=30)
rpa = models.ForeignKey(RPA, on_delete=models.CASCADE)
## Crew Details
user = models.ForeignKey(User, on_delete=models.CASCADE)
USER_ROLE_CHOICES = [
('RPIC', 'Remote Pilot-In-Command'),
('RP', 'Remote Pilot'),
('SPOT', 'Spotter'),
('CAMOP', 'Camera operator'),
]
user_role = models.CharField(
max_length=5,
choices=USER_ROLE_CHOICES,
)
crew = models.CharField(max_length=128, blank=True)
crew_role = models.CharField(
max_length=5,
choices=USER_ROLE_CHOICES,
blank = True
)
## Flight Details
launch_location = models.CharField(max_length=128)
recovery_location = models.CharField(max_length=128)
remarks = models.CharField(max_length=256, blank=True)
vlos = models.BooleanField(default=True)
evlos1 = models.BooleanField(default=False)
evlos2 = models.BooleanField(default=False)
bvlos = models.BooleanField(default=False)
## Flight Time
ft_day = FlightTimeFieldInt("Day flight time", blank=True, null=True,)
ft_night = FlightTimeFieldInt("Night flight time", blank=True, null=True,)
def _ft_total(self):
return int(self.ft_day + self.ft_night)
ft_total = property(_ft_total)
## Category of mission or flight
MISSION_CATEGORY_CHOICES = [
('INS', 'Inspection'),
('MAP', 'Mapping'),
('PHO', 'Photography'),
('VID', 'Videography'),
('PRI', 'Private flying'),
('TRA', 'Training'),
]
mission_category = models.CharField(
max_length=3,
choices=MISSION_CATEGORY_CHOICES,
blank=True,
)
## Landings & Autolands
no_of_landings_day = models.IntegerField("Number of landings", blank=True, default=0,)
no_of_landings_night = models.IntegerField("Number of auto landings", blank=True, default=0,)
def get_absolute_url(self):
return reverse('ihq-rplogbook-list')
def clean(self):
if self.no_of_landings_day is None and self.no_of_landings_night is None:
raise ValidationError({'no_of_landings_day':_('You must have landed at least once!')})
if self.no_of_landings_night is None:
if self.no_of_landings_day >= 1:
self.no_of_landings_night = 0
return self.no_of_landings_night
if self.no_of_landings_day is None:
if self.no_of_landings_night >= 1:
self.no_of_landings_day = 0
return self.no_of_landings_day
def clean_fields(self, exclude=None):
validation_error_dict = {}
p = re.compile('^[0-9]+:([0-5][0-9])')
if self.ft_day !='' and p.match(self.ft_day) is None:
validation_error_dict['ft_day'] = ValidationError(_('Flight duration must be in HH:MM format.'))
if self.ft_night !='' and p.match(self.ft_night) is None:
validation_error_dict['ft_night'] = ValidationError(_('regex night wrong'))
if self.ft_day =='' and self.ft_night =='':
validation_error_dict['ft_day'] = ValidationError(_('Fill in at least one flying time duration.'))
if (self.vlos or self.evlos1 or self.evlos2 or self.bvlos) is False:
validation_error_dict['vlos'] = ValidationError(_('Select one LOS category.'))
if (self.vlos or self.evlos1 or self.evlos2 or self.bvlos) is True:
counter = 0
for x in [self.vlos, self.evlos1, self.evlos2, self.bvlos]:
if x == True:
counter += 1
if counter > 1:
validation_error_dict['vlos'] = ValidationError(_('Selecting more than one LOS category is not allowed.'))
# if self.no_of_landings is None and self.no_of_auto_landings is None:
# validation_error_dict['no_of_landings'] = ValidationError(_('You must have landed at least once!'))
if validation_error_dict:
raise ValidationError(validation_error_dict)
Basically my question is - shouldn't this UpdateView populate my form automatically?
Very silly mistake on my part.
I was using the same HTML template to add and edit these records, which had the {{form.cleaned_data.field_name}} in the <input value="..> tag. This works for retaining data when adding a new record, such as an invalid form, but won't show up when editing an object.
The fix was to create a new template (identical page), but fill the <input value="... with the necessary object value template tag i.e. {{object.field_name}}.

Django - expected type pk, received str [Many to many]

I have a webapp where we can create communities with django as a backend, but when i try to send a POST to create a community, I get:
community_configuration: ["Incorrect type. Expected pk value, received str."]
My POST:
{
title: knkn kn .k jbjnmn,
logo: [object File],
is_active: true,
description: test,
welcome_message: Welcome message,
org_id: 114,
community_configuration: About us,Community news,FAQs,Supporters,Resources,
}
Here are my serializers:
class MicroConfigurationSerializer(serializers.ModelSerializer):
class Meta:
model = MicroConfiguration
fields = [
'name',
]
def to_representation(self, instance):
return instance.name
class CommunityConfigurationSerializer(serializers.ModelSerializer):
micro_configurations = MicroConfigurationSerializer(many=True, read_only=True)
class Meta:
model = CommunityConfiguration
fields = '__all__'
class CommunitySerializer(serializers.ModelSerializer):
logo = serializers.SerializerMethodField()
organisation = serializers.SerializerMethodField()
community_configuration = CommunityConfigurationSerializer()
class Meta:
model = Community
fields = (
...
etcetc
...
)
Heres my model:
class MicroConfiguration(core_models.BaseModel):
name = models.CharField(max_length=32, unique=True)
permissions = models.ManyToManyField(Permission)
def __str__(self):
return self.name
class CommunityConfiguration(core_models.BaseModel):
name = models.CharField(max_length=32)
micro_configurations = models.ManyToManyField(MicroConfiguration)
permission_group = models.ForeignKey(
Group,
on_delete=models.SET_NULL,
null=True
)
def __str__(self):
return self.name
class Community(core_models.BaseModel):
accounts = models.ManyToManyField(
settings.AUTH_USER_MODEL,
through='associates.AccountCommunity',
through_fields=('community', 'account')
)
goals = models.ManyToManyField(Goal, through='associates.CommunityGoal')
type = models.ManyToManyField(CommunityType, through='CommunityAttribute')
communities = models.ManyToManyField(
'self',
through='associates.CommunityCommunity',
symmetrical=False,
)
crossposts = models.ManyToManyField(
Action,
through='activities.CommunityCrosspost',
through_fields=('community', 'action'),
related_name='communities',
)
title = models.CharField(max_length=64)
logo = models.ImageField(null=True, blank=True)
intro_media = models.ForeignKey(
'associates.MediaStore',
null=True,
on_delete=models.SET_NULL
)
website_url = models.CharField(max_length=256, blank=True)
is_invite_only = models.BooleanField(default=False)
description = models.TextField(blank=True)
intro_message = models.TextField(blank=True)
welcome_message = models.TextField(blank=True)
signup_autojoin = models.BooleanField(default=False)
community_configuration = models.ForeignKey(
CommunityConfiguration,
default=1,
null=True,
on_delete=models.SET_NULL,
help_text='Do not edit directly, create a new custom config instead, \
as it is reference by difference community.',
)
objects = models.Manager()
associates = AssociationManager()
#property
def url(self):
return reverse(
'communities:detail',
kwargs={
'id': self.id,
}
)
class Meta:
verbose_name = 'Community'
verbose_name_plural = 'Communities'
def __str__(self):
return self.title
Here's my views:
class CommunityCreateAPIView(CreateAPIView):
serializer_class = CommunityCreateSerializer
def create(self, request, **kwargs):
organisation_id = request.data.get('org_id')
micro_configurations_qd = request.data.copy()
micro_configurations = micro_configurations_qd.pop('community_configuration', False)
if micro_configurations:
micro_configurations = micro_configurations[0].split(',')
user = request.user
data = {}
permitted = AccountOrganisation.objects.filter(
account=user,
organisation_id=organisation_id,
is_active=True,
association__permissions__codename='add_community',
).exists()
if not permitted and not request.user.is_superuser:
data['message'] = 'Action not allowed'
return Response(
data=data,
status=status.HTTP_400_BAD_REQUEST,
)
response = super().create(request, **kwargs)
if response.status_code == 400:
data['message'] = 'Failed to update community'
return Response(
data=data,
status=status.HTTP_400_BAD_REQUEST,
)
community_id = response.data.get('id')
OrganisationCommunity.objects.create(
community_id=community_id,
organisation_id=organisation_id,
)
association = Group.objects.get(name='Community Admin')
AccountCommunity.objects.create(
community_id=community_id,
account=user,
association=association,
)
if micro_configurations:
com_config_qs = CommunityConfiguration.objects.filter(
micro_configurations__name__in=micro_configurations
).annotate(
num_micro_config=Count('micro_configurations__name')
).filter(
num_micro_config=len(micro_configurations)
)
community_configuration = None
if micro_configurations:
for com_config in com_config_qs:
micro_config_count = com_config.micro_configurations.count()
if micro_config_count == len(micro_configurations):
community_configuration = com_config
break
if community_configuration:
Community.objects.filter(
pk=community_id
).update(
community_configuration=community_configuration
)
elif micro_configurations:
micro_qs = MicroConfiguration.objects.filter(
name__in=micro_configurations
)
community_config = CommunityConfiguration.objects.create(
name='Custom'
)
community_config.micro_configurations.set(micro_qs)
community_config.save()
Community.objects.filter(
pk=community_id
).update(
community_configuration=community_config
)
return response
I've been stuck with this for hours, any help?
Your serializer expects a pk (or id) of the community_configuration instance.
basically you have it set up so that you need to create a community_configuration entry first, then fetch the id of the new created entry and use that id when creating your community.
If you want to have the community_configuration instance to be created while creating the community then an option to do that would be to override the create method in the serializer and extract the community_configuration data then create a new entry for that model and use it, something like this
class CommunitySerializer(serializers.ModelSerializer):
# .. put your serializer code here
def create(self, validated_data):
community_configuration= validated_data.pop('community_configuration')
config_instance, created = CommunityConfiguration.objects.get_or_create(<community_configuration>) # modify this however you need to create the CommunityConfiguration instance
community_instance = community.objects.create(**validated_data, community_configuration=config_instance)
return community_instance
you will need to probably modify the code for it to follow your needs.
I didn't read all of your models, not sure how you are gonna create the nested models from the input value but you get the point.

Setting value of Django M2M through relationship via ModelForm

I am working on a set of product / category relationships in a Django application.
A product can belong to any category and needs to be ordered within that category, I am trying to do this using a Many to Many relationship with a "through=" option.
When a POST request is made via Ajax it takes the form of b'ordered_products=4&ordered_products=5', I received an error straight after the forms __init__ call that "5 is not one of the available choices" where 5 is the valid id of anOrderedCategoryManagedProduct object.
models.py
class Category(models.Model):
name = models.CharField(max_length=128)
slug = models.SlugField(max_length=128, unique=True)
class Product(models.Model):
name = models.CharField(max_length=128)
category_management = models.ManyToManyField(
Category,
related_name="category_managed_products",
through="OrderedCategoryManagedProduct",
blank=True,
verbose_name="Category Management",
)
class OrderedCategoryManagedProduct(SortableModel):
category = models.ForeignKey(
Category, on_delete=models.CASCADE, related_name="cm_products"
)
product = models.ForeignKey(
Product, on_delete=models.CASCADE, related_name="cm_categories"
)
class Meta:
ordering = ["sort_order"]
def get_ordering_queryset(self):
return self.product.category_management()
class SortableModel(models.Model):
sort_order = models.IntegerField(db_index=True, null=True)
class Meta:
abstract = True
views.py
# POST = <QueryDict: {'ordered_products': [5, 4]}>
#staff_member_required
#permission_required("menu.manage_menus")
def ajax_reorder_menu_items(request, category_pk):
category = get_object_or_404(Category, pk=category_pk)
form = ReorderCategoryProductsForm(request.POST, instance=category)
status = 200
ctx = {}
if form.is_valid():
form.save()
elif form.errors:
status = 400
ctx = {"error": form.errors}
return JsonResponse(ctx, status=status)
forms.py
class ReorderCategoryProductsForm(forms.ModelForm):
ordered_products = OrderedModelMultipleChoiceField(
queryset=OrderedCategoryManagedProduct.objects.none()
)
class Meta:
model = Category
fields = ["id"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance:
self.fields["ordered_products"].queryset = self.instance.cm_products.all()
pass
def save(self):
for sort_order, category in enumerate(self.cleaned_data["ordered_products"]):
category.cm_products.sort_order = sort_order
category.save()
return self.instance
class OrderedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def clean(self, value):
qs = super().clean(value)
keys = list(map(int, value))
return sorted(qs, key=lambda v: keys.index(v.pk))
Big thanks to the #django IRC channel for the help on this, if anyone suffers the same problem the correct code was as follows:
class ReorderCategoryProductsForm(forms.ModelForm):
ordered_products = OrderedModelMultipleChoiceField(
queryset=OrderedCategoryManagedProduct.objects.all()
)
class Meta:
model = Category
fields = ["id"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance:
self.fields["ordered_products"].queryset = self.instance.cm_products.all()
pass
def save(self):
for sort_order, ocmp in enumerate(self.cleaned_data["ordered_products"]):
ocmp.sort_order = sort_order
ocmp.save()
return self.instance

django complex formset issue

I have 3 primary models. Questionnaire model or question set contains set of questions. All user response are stored in answer.
Now I have to generate a formset which will store answer of all questions in a questionnaire set. How can I do it in django. So far I have manged to do it by displaying single question at once from a given questionnaire and store the response. My problem is that based on questiontype use two different modelform (MultipleChoiceAnswerForm,DescriptiveChoiceAnswerForm) and validate them based on the formtype. How can I use it in formset.
I am beginner in django and any help is appreciated.
My Code:
#Models.py
class Question(models.Model):
statement = models.CharField(max_length=255)
question_type = models.CharField(max_length=20, choices=get_qtypes())
remarks = models.CharField(max_length=200, null=True, blank=True)
def __unicode__(self):
return '%s'%(self.statement)
class Questionnaire(models.Model):
title = models.CharField(max_length=255)
questionaire_type = models.CharField(max_length=20,choices=QUESTIONNAIRETYPE)
context = models.ForeignKey(QuestionContext)
questions = models.ManyToManyField(Question)
timestamp = models.DateTimeField(auto_now=True)
tathya_user = models.ForeignKey(User)
def __unicode__(self):
return '%s'%(self.title)
class Answer(models.Model):
question = models.ForeignKey(Question)
person = models.ForeignKey(Person)
course = models.ForeignKey(Course)
teacher=models.ForeignKey(Person, null=True, blank=True, default = None)
questionaire = models.ForeignKey(Questionnaire)
statement = models.CharField(max_length=255)
def get_label(self):
return '%s'%(self.question.statement)
def get_choices(self):
return get_questionchoices(self.question.question_type)
class DescriptiveAnswerForm(ModelForm):
def __init__(self, *args, **kwargs):
super(DescriptiveAnswerForm, self).__init__(*args, **kwargs)
if kwargs.has_key('instance'):
self.fields['statement'].label = kwargs['instance'].get_label()
statement = forms.CharField(widget=forms.Textarea())
class Meta:
model = Answer
exclude=('question','person','course','teacher','questionaire')
class MultipleChoiceAnswerForm(ModelForm):
statement = forms.ChoiceField(widget=forms.RadioSelect(choices=EMPTY,attrs={'class': 'allradio',}))
def __init__(self, *args, **kwargs):
super(MultipleChoiceAnswerForm, self).__init__(*args, **kwargs)
if kwargs.has_key('instance'):
self.fields['statement'].label = kwargs['instance'].get_label()
self.fields['statement'].choices = kwargs['instance'].get_choices()
class Meta:
model = Answer
exclude=('question','person','course','teacher','questionaire')
###################################################################
#view.py
#login_required
def content_feedback_view_old(request,course_code):
#do validation and other jobs
questionnaire = get_questionnaire(some_params_like_coursecode)
if request.method == 'POST':
r_answer = Answer()
r_answer.question = Question.objects.get(id=request.session['question'])
r_answer.person = student
r_answer.course = course
r_answer.questionaire = questionnaire
r_answer.tathya_user = User.objects.get(id=request.user.pk)
rformtype = request.POST['formtype']
if rformtype == 'MCQ':
rform = MultipleChoiceAnswerForm(request.POST, instance=r_answer)
else:
rform = DescriptiveAnswerForm(request.POST, instance=r_answer)
if rform.is_valid():
rform.save()
else:
#return HttpResponse(printerror("Some problem occurred!"))
errortext = "You need to provide an input!"
questions = questionnaire.questions.all()
allquestions = questions.count()
tot_q = 0
formtype = ""
answered = 0
for question in questions:
try:
answer=Answer.objects.get(question=question,person=student,course=course,questionaire=questionnaire)
answered += 1
except:
answer = Answer()
answer.question = question
answer.person = student
answer.course = course
answer.questionaire = questionnaire
answer.tathya_user = User.objects.get(id=request.user.pk)
request.session['question']=question.id
tot_q = tot_q + 1;
if get_questiontype(question.question_type)=='MCQ':
formtype="MCQ"
form=MultipleChoiceAnswerForm(instance=answer)
else:
formtype="DESC"
form=DescriptiveAnswerForm(instance=answer)
break
if tot_q>0:
data_dict['FeedbackFormType']=formtype
data_dict['FeedbackForm']=form
data_dict['pagetitle']=context.description
data_dict['coursecode']=course.course_code
data_dict['feedbacktitle']="Content Feedback for "+course.fullname
data_dict['Completeness'] = (answered/allquestions)*100
data_dict['error']=errortext
else:
return HttpResponse(printerror("Thanks! You've answered all the questions!<br>Continue with the teaching feedback."))
req_context = RequestContext(request)
return render_to_response('view.html', data_dict, req_context)
Simple answer: only use on single AnswerForm and let it manage which kind of field and widget it should use, ie:
class AnswerForm(ModelForm):
def __init__(self, *args, **kwargs):
super(AnswerForm, self).__init__(*args, **kwargs)
instance = self.instance
if instance.question.question_type == 'MCQ':
self.fields["statement"] = forms.ChoiceField(
choices=instance.get_choices(),
widget=forms.RadioSelect(attrs={'class': 'allradio',})
)
else:
self.fields["statement"] = forms.CharField(
widget=forms.Textarea()
)
self.fields['statement'].label = instance.get_label()
class Meta:
model = Answer
exclude=('question','person','course','teacher','questionaire')
As a side note, you can pass model's attributes values to the model's constructor:
answer = Answer(
question=Question.objects.get(id=request.session['question']),
person=student,
course=course,
questionnaire=questionnaire,
# User.objects.get(id=request.user.pk) will return request.user
# so it's just useless - just use request.user
tathya_user=request.user
)

Categories

Resources