I designed and built a Django 1.6.2 survey application using a SessionWizardView which is connected to a MySQL database.
The problem is that (as far as I can see) the submitted form data is not getting saved to the database. This is my first time building an application like this or even working with a database.
Could someone take a look at what I have done and my code and point out any mistakes I have made as to why I cannot see any content submitted by my survey form?
The last time I posted a similar question I was told I needed to create a Model for the data and it was suggested I use a ModelForm to create the table and columns in the database. I have done this but I am still not seeing my submitted content
My Process
I created the database in MySQL (Ver 14.14 Distrib 5.6.20) via
Terminal CREATE database django_db; and the tables in it are created
when I run the command python manage.py syncdb.
I can complete my survey both on my local machine and on the public server. No errors and it appears everything works fine
I have setup phpMyAdmin and can see the django_db database and survey_person model. However I can not seem to find any of the data that should be submitted by the form.
I have tried to use the search facility in phpMyAdmin to find any of the form data I submitted but cannot see it
I have exported the database as a .CSV file but it is empty.
If I use the insert facility in phpMyAdmin the data gets saved in the DB and I can return it when I use the search facilities. The same data is also in the CSV file when I export it.
This seem to suggest that I am missing a step somewhere in my application when it comes to submitting content to the DB.
Can anyone tell me where I am going wrong?
My Code
I have tried to limit the below to only relevant code
settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django_db',
'USER': 'root',
'PASSWORD': 'xxxxxxxxxxxxxxxxx',
'HOST': '127.0.0.1',
#'PORT': '',
}
}
urls.py
url(r'^surveyone/$', SurveyWizardOne.as_view([
SurveyFormA,
SurveyFormB,
SurveyFormC,
....
....
SurveyFormG,
SurveyFormH,
SurveyFormI
])),
forms.py
class SurveyFormA(forms.ModelForm):
birthdate = forms.DateField(widget=extras.SelectDateWidget(years = range(1995, 1900, -1)), required=False)
class Meta:
model = Person
fields = ['sender', 'birthdate', 'sex', 'relationship', 'state']
class SurveyFormB(forms.ModelForm):
class Meta:
model = Person
fields = ['internet_usage', 'smart_phone_ownership', 'smart_phone_usage']
widgets = {'internet_usage' : RadioSelectNotNull,
'smart_phone_ownership' : RadioSelectNotNull,
'smart_phone_usage' : RadioSelectNotNull,
}
class SurveyFormC(forms.ModelForm):
class Meta:
model = Person
fields = ['education', 'wages', 'presentage_savings', 'occupation', 'living']
widgets = {'education' : forms.RadioSelect,
'wages' : forms.RadioSelect,
'presentage_savings' : forms.RadioSelect,
'occupation' : forms.RadioSelect,
'living' : forms.RadioSelect,}
....
....
models.py
sender = models.EmailField(null=True, blank=True, verbose_name='What is your email address?')
birthdate = models.DateField(null=True, blank=True) #overwritten in forms.py so passed no more arguments
SEX = (
('MALE', 'Male'),
('FEMALE', 'Female'))
sex = models.CharField(null=True, blank=True, max_length=100, choices=SEX, verbose_name='What sex are you?')
RELATIONSHIP = (
('SINGLE', "Single"),
('INARELATIONSHIP', "In a relationship"),
('MARRIED', "Married"),
('DIVORCED', "Divorced"),
('SEPARATED', "Separated"),
('WIDOWED', "Widowed"),)
relationship = models.CharField(null=True, blank=True, max_length=100, choices=RELATIONSHIP, verbose_name='What is your relationship status?')
....
....
def __unicode__(self):
return self
views.py
My views.py are the most complex part of my application. Not sure if it necessary to show any but I thought just in case
class SurveyWizardOne(SessionWizardView):
def get_context_data(self, form, **kwargs):
context = super(SurveyWizardOne, self).get_context_data(form, **kwargs)
step = int(self.steps.current)
if step == 0:
self.request.session['path_one_images'] = ['P1D1.jpg', 'P2D2.jpg', 'P3D3.jpg', 'P4D4.jpg', 'P5D5.jpg', 'P6D6.jpg', 'P7D7.jpg', 'P8D8.jpg', 'P9D9.jpg']
self.request.session['instruction_task_one_images'] = ['IT1A.jpg', 'IT1B.jpg', 'IT1C.jpg']
self.request.session['instruction_task_two_images'] = ['IT2A.jpg', 'IT2B.jpg', 'IT2C.jpg']
self.request.session['images'] = []
self.request.session['slider_DV_values'] = []
PATH_ONE_IMAGES = self.request.session.get('path_one_images', [])
images = self.request.session.get('images', [])
slider_DV_values = self.request.session.get('slider_DV_values', [])
INSTRUCTION_TASK_ONE_IMAGES = self.request.session.get('instruction_task_one_images', [])
INSTRUCTION_TASK_TWO_IMAGES = self.request.session.get('instruction_task_two_images', [])
if step in range (0, 27):
self.request.session['path_one_images'] = PATH_ONE_IMAGES
self.request.session['images'] = images
self.request.session['slider_DV_values'] = slider_DV_values
self.request.session['instruction_task_one_images'] = INSTRUCTION_TASK_ONE_IMAGES
self.request.session['instruction_task_two_images'] = INSTRUCTION_TASK_TWO_IMAGES
if step == 0:
instruction_task_first_image = random.choice(INSTRUCTION_TASK_ONE_IMAGES)
context['display_image'] = instruction_task_first_image
elif step == 1:
instruction_task_second_image = random.choice(INSTRUCTION_TASK_TWO_IMAGES)
context['display_image'] = instruction_task_second_image
elif step == 9:
first_image = random.choice(PATH_ONE_IMAGES)
PATH_ONE_IMAGES.remove(first_image)
context['display_image'] = first_image
images.insert(0, first_image)
self.request.session['first_image'] = images[0]
self.request.session.get('first_image')
elif step == 10:
second_image = random.choice(PATH_ONE_IMAGES)
PATH_ONE_IMAGES.remove(second_image)
....
....
return context
def done(self, form_list, **kwargs):
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
phpMyAdmin
A screenshot of the DB from phpMyAdmin. NOTE: Not the hidden fields at the start are for introduction steps in the survey form, code not shown here for brevity.
Submitting data to a model form does not cause it to be saved automatically. If you want save data from a model form to the database, you need to call its save() method. You could do this in the wizard's done() method.
def done(self, form_list, **kwargs):
# I don't know whether this will work, I am not familiar with your forms.
for form in form_list:
form.save()
# Note the docs suggest redirecting instead of rendering a template.
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
Related
I do coverage report and there are many place that not cover, do you have any idea how to I write test for th?
this views.py was not cover
def addreview(request, u_id, shop_id):
url = request.META.get('HTTP_REFERER')
shop = shop_detail.objects.get(id=shop_id)
user = Profile.objects.get(customer=u_id)
if request.method == 'POST':
form = ReviewForm(request.POST)
if form.is_valid():
data = Review()
data.review_text = form.cleaned_data['review_text']
data.review_rating = form.cleaned_data['review_rating']
data.shop = shop
data.user = user
data.save()
return redirect(url)
def rating(request):
if not request.user.is_authenticated:
return HttpResponseRedirect(reverse('customer_login'))
if request.method == 'POST':
form = RateUsForm(request.POST)
if form.is_valid():
rate = RateUs()
rate.rate_text = form.cleaned_data['rate_text']
rate.rating = form.cleaned_data['rating']
rate.user = request.user
rate.save()
return HttpResponseRedirect(reverse("index"))
return render(request, 'shop/rate.html')
models.py
class Review(models.Model):
user = models.ForeignKey(Profile,on_delete=models.CASCADE)
shop = models.ForeignKey(shop_detail,on_delete=models.CASCADE)
review_text = models.TextField(max_length=300)
review_rating = models.IntegerField()
def __str__(self):
return f"{self.review_rating}"
class RateUs(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
rate_text = models.TextField()
rating = models.IntegerField()
def __str__(self):
return f"{self.rating}"
forms.py
class ReviewForm(forms.ModelForm):
class Meta:
model= Review
fields= ["review_text", "review_rating"]
class RateUsForm(forms.ModelForm):
class Meta:
model= RateUs
fields= ["rate_text","rating"]
This is what I write for rating
def test_valid_rating(self):
rateus1 = RateUs.objects.first()
data={
"user": rateus1.user,
"rate_text": rateus1.rate_text,
"rating": rateus1.rating
}
response = self.client.post(reverse('rating'), data=data)
self.assertEqual(response.status_code, 302)
When I try to write test for addreview i got customer.models.Profile.DoesNotExist: Profile matching query does not exist.
Here is what you need to implement to get a way to test your function:
# Imports needed
import pytest
from unittest.mock import MagicMock, patch
def test_addreview(self):
# Here probably you will need to create a User before.
user = User.objects.create(username='testuser', id=15445)
shop = shop_detail.objects.create(id=19889)
Profile.objects.create(customer=user.pk)
mock_request = MagicMock(
POST={'review_text': 'test', 'review_rating': 5},
META={'HTTP_REFERER': 'http://testserver/'},
method='POST',
)
self.assert(Review.objects.count(), 0)
with patch('django.shortcuts.redirect') as redirect_mock:
addreview(mock_request, user.pk, shop.pk)
redirect_mock.assert_called_with('http://testserver/')
self.assert(Review.objects.count(), 1)
def test_addreview_not_redirected(self):
# Here probably you will need to create a User before.
user = User.objects.create(username='testuser', id=15445)
shop = shop_detail.objects.create(id=19889)
Profile.objects.create(customer=user.pk)
mock_request = MagicMock(
POST={'review_text': 'test', 'review_rating': 5},
META={'HTTP_REFERER': 'http://testserver/'},
method='GET',
)
self.assert(Review.objects.count(), 0)
with patch('django.shortcuts.redirect') as redirect_mock:
addreview(mock_request, user.pk, shop.pk)
redirect_mock.assert_not_called()
self.assert(Review.objects.count(), 0)
def test_addreview_raise_exception_profile_not_found(self):
# Here probably you will need to create a User before.
user = User.objects.create(username='testuser', id=15445)
shop = shop_detail.objects.create(id=19889)
mock_request = MagicMock(
POST={'review_text': 'test', 'review_rating': 5},
META={'HTTP_REFERER': 'http://testserver/'},
method='GET',
)
self.assert(Review.objects.count(), 0)
self.assert(Profile.objects.count(), 0)
with pytest.raises(Profile.DoesNotExist):
addreview(mock_request, user.pk, shop.pk)
self.assert(Review.objects.count(), 0)
self.assert(Profile.objects.count(), 0)
Probaly you will need to adapt a little bit each example as it is just a guide of how you have to implement it.
When working with this kind of test if you expect for example a Profile to exists you need to create it before to anticipate to the lines of code that will be executed once the function is called.
Mock are so important in this case we mock request and simulate all the properties that a request has.
We also mock redirect but in this case just to knwo if it was called or not.
At the end we generate a case where what we expect is the code to generate an exception when calling the funcion.
Normally to create model objects in test the best practice is to use https://factoryboy.readthedocs.io/en/stable/ take a look of that.
I'm working on an Django 3 app which allows users to add and remove data requests for review. The user will select the user and site from a list and depending on the availability of data will populate a date/time field to be able to select a start and end date range. I'm using the Tempus Dominus time date picker to set the available dates in the date picker and am hardcoding them for the time being. The end goal will be to query Elasticsearch for them but hardcoded is fine for now.
My question is; At the moment the form is static, meaning none of the selections change the content in other sections of the form.
How can i make it dynamic so that the list of enableDates dates changes when the user selects a different user or site from the dropdown lists? Just to be clear, the list of users and sites will never change, it's just the enableDates that need to change.
I'd appreciate any help or advice.
At the moment the form for submitting a request, along with displaying any existing requests looks like:
This is my MODEL for storing data requests:
class DataRequest(models.Model):
USERS = [
('User1', 'User1'),
('User2', 'User2'),
('User3', 'User3'),
]
users = models.CharField(
choices=USERS,
max_length=32,
default='User1',
)
SITE = [
('Site1', 'Site1'),
('Site2', 'Site2'),
('Site3', 'Site3'),
]
site =models.CharField(
choices=SITE,
max_length=32,
default='Site1',
)
start = models.DateTimeField()
end = models.DateTimeField()
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
def get_request(self):
request = {
"id": self.id,
"users": self.users,
"site": self.site,
"start": self.start,
"end": self.end,
}
return request
This is the FORM:
class RequestForm(ModelForm):
start = forms.DateTimeField(
widget=DateTimePicker(
options={
},
attrs={
'append': 'fa fa-calendar',
'input_toggle': True,
'icon_toggle': True,
}
)
)
end = forms.DateTimeField(
widget=DateTimePicker(
options={
'useCurrent': False,
'enabledDates':["2021-02-20",], // hard coded dates go here
},
attrs={
'append': 'fa fa-calendar',
'input_toggle': True,
'icon_toggle': True,
}
)
)
class Meta:
model = DataRequest
fields = ['users','site', 'start','end']
def __init__(self, *args, **kwargs):
super(RequestForm, self).__init__(*args, **kwargs)
self.fields['users'].widget.attrs.update({'class' : 'form-control'})
self.fields['site'].widget.attrs.update({'class' : 'form-control'})
this is the VIEW for adding and removing requests, the list of existing data requests is sent over in the page context:
def delRequest(request, pk):
if request.method == 'POST':
request = DataRequest.objects.get(id=pk)
request.delete()
return redirect('dashboard')
def addRequest (request):
if request.method == 'POST':
form = RequestForm(request.POST)
if form.is_valid():
DataRequest.objects.create(
user=request.user,
users=form.cleaned_data['users'],
site=form.cleaned_data['site'],
start=form.cleaned_data['start'],
end=form.cleaned_data['end']
)
print("Added new Request")
return redirect('dashboard')
I have two models like this:
class Sector(models.Model):
name = models.CharField(max_length=100, db_index=True, unique=True) # HERE IF I REMOVE unique=True, it works correctly
class Address(models.Model):
...
sector = models.ForeignKey(Sector, null=True, blank=True)
And a serializer for the Address model:
In the view, I have this:
address_serialized = AddressSerializer(data=request.data)
if address_serialized.is_valid():
address_serialized.save(client=client)
It never gets to the create function. I have a serialized with a create function that looks like this:
class AddressSerializer(serializers.ModelSerializer):
city_gps = CitySerializer(required=False)
sector = SectorSerializer(required=False)
class Meta:
model = Address
fields = (..., "sector")
def create(self, validated_data):
...
sector_dict = validated_data.get("sector", None)
sector = None
if sector_dict and "name" in sector_dict and city_gps:
if Sector.objects.filter(name=sector_dict["name"], city=city_gps).exists():
sector = Sector.objects.get(name=sector_dict["name"], city=city_gps)
# pdb.set_trace()
if "sector" in validated_data:
validated_data.pop("sector")
if "city_gps" in validated_data:
validated_data.pop("city_gps")
address = Address.objects.create(sector=sector, city_gps=city_gps, **validated_data)
return address
The code never touches this function, is_valid() returns False. And the message is
{"sector":{"name":["sector with this name already exists."]}}
I need to be able to create a new address with FK to the already existing sector. How can I achieve that? Any advice will help.
EDIT
The view looks like this:
class ClientProfileAddressCreateView(APIView):
# throttle_scope = '1persecond'
renderer_classes = (JSONRenderer,)
permission_classes = (IsAuthenticated,)
def post(self, request):
try:
client = Client.objects.get(user=request.user)
except ObjectDoesNotExist:
return Response({"error": "A client profile for the logged user does not exit"},
status=status.HTTP_404_NOT_FOUND)
address_serialized = AddressSerializer(data=request.data)
print("address_serialized.is_valid: %s" % address_serialized.is_valid()) # Returns False when unique=True in models
if address_serialized.is_valid():
# print("address_serialized: %s" % address_serialized.data)
address_serialized.save(client=client)
else:
return Response(data=address_serialized.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(data=address_serialized.data, status=status.HTTP_201_CREATED)
This is a known issue with nested serializers and unique constraints.
Really awesome thing to always do is actually print the Serializer - that can give you a lot of extra info.
When you have a json like this:
{
"Sector": {
"name": "Sector XYZ"
},
"address_line_one": “Some Random Address”
}
Django REST framework does not know whether you're creating or getting the Sector object, thus it forces validation on every request.
What you need to do is the following:
class SectorSerializer(serializers.ModelSerializer):
# Your fields.
class Meta:
model = Address
fields = ("Your Fields",)
extra_kwargs = {
'name': {
'validators': [],
}
}
Then to handle validation you would need to redo the create/update part to fit the uniqueness constraint and raise exception/validation error.
I hope this helps.
Helpful links: This SO Answer and Dealing with unique constraints in nested serializers
EDIT :
As per cezar's request: I will add how it might look like to override the create method of the serializer. I have not tried this code, but the logic goes like this.
class SectorSerializer(serializers.ModelSerializer):
# Your fields.
class Meta:
model = Address
fields = ("Your Fields",)
extra_kwargs = {
'name': {
'validators': [],
}
}
def create(self, validated_data):
raise_errors_on_nested_writes('create', self, validated_data)
ModelClass = self.Meta.model
info = model_meta.get_field_info(ModelClass)
many_to_many = {}
for field_name, relation_info in info.relations.items():
if relation_info.to_many and (field_name in validated_data):
many_to_many[field_name] = validated_data.pop(field_name)
# FIELD CHECK
your_field = validated_data.get("your_field","") # or validated_data["your_field"]
try:
YourModel.objects.filter(your_check=your_field).get()
raise ValidationError("Your error")
except YourModel.DoesNotExist:
# if it doesn't exist it means that no model containing that field exists so pass it. You can use YourQuerySet.exists() but then the logic changes
pass
try:
instance = ModelClass.objects.create(**validated_data)
except TypeError:
tb = traceback.format_exc()
msg = (
'Got a `TypeError` when calling `%s.objects.create()`. '
'This may be because you have a writable field on the '
'serializer class that is not a valid argument to '
'`%s.objects.create()`. You may need to make the field '
'read-only, or override the %s.create() method to handle '
'this correctly.\nOriginal exception was:\n %s' %
(
ModelClass.__name__,
ModelClass.__name__,
self.__class__.__name__,
tb
)
)
raise TypeError(msg)
# Save many-to-many relationships after the instance is created.
if many_to_many:
for field_name, value in many_to_many.items():
field = getattr(instance, field_name)
field.set(value)
return instance
with django 1.5.1 I try to use the django form for one of my models.
I dont want to add the "user" field (Foreignkey) somewhere in the code instead of letting the user deceide whoes new character it is.
My Code:
Model:
class Character(models.Model):
user = models.ForeignKey(User)
creation = models.DateTimeField(auto_now_add=True, verbose_name='Creation Date')
name = models.CharField(max_length=32)
portrait = models.ForeignKey(Portrait)
faction = models.ForeignKey(Faction)
origin = models.ForeignKey(Origin)
The form:
class CreateCharacterForm(forms.ModelForm):
class Meta:
model = Character
fields = ['name', 'portrait', 'faction', 'origin']
The view:
def create_character(request, user_id):
user = User.objects.get(id=user_id)
if request.POST:
new_char_form = CreateCharacterForm(request.POST)
if new_char_form.is_valid():
new_char_form.save()
return HttpResponseRedirect('%s/characters/' % user_id)
else:
return render_to_response('create.html',
{'user': user, 'create_char':new_char_form},
context_instance=RequestContext(request))
else:
create_char = CreateCharacterForm
return render_to_response('create.html',
{'user': user, 'create_char': create_char},
context_instance=RequestContext(request))
I have tried to use a instance to incluse the userid already. i've tried to save the userid to the form before saving it, or changing the save() from my form.
I keep getting the error that character.user cant be null
I have to tell that im pretty new to django and im sure one way or another it should be possible
Can someone please help me out?
Its explained well in document model form selecting fields to use
You have to do something like this in your view
...
if request.POST:
new_char_form = CreateCharacterForm(request.POST)
if new_char_form.is_valid():
#save form with commit=False
new_char_obj = new_char_form.save(commit=False)
#set user and save
new_char_obj.user = user
new_char_obj.save()
return HttpResponseRedirect('%s/characters/' % user_id)
else:
...
I’m facing my first encoding-related bug and I’m stumped. The problem is in a Django app with a form which updates existing database entries. When the update form is used to make changes to an entry, I’m finding that the new entry data includes encoding-related notations like quotation marks, parentheses, and the u (encoding mark). It appears that I’ve made some sort of mistake which confuses Django about what encoding my strings are in.
To summarize, this is the problem:
Initial state: Entry title field is FooBar
Desired state: Entry title field is FooBar2
What I get instead: Entry title field is (u’FooBar2’,)
These encoding markers are getting printed out in my Django app, and I confirmed that they're also there when I access the database directly from the Python shell (as in the example which follows)
>>>entry.title
u"(u'FooBar2',)"
Any idea why my form and view are saving these notations into the database? How can I stop it?
This is the form I’m using to edit the database entries:
class EntryEditForm(forms.Form):
title = forms.CharField(label=u'Entry title.', max_length=100)
target = forms.CharField(label=u'Entry target', required=False)
username = forms.CharField(label=u'Entry-specific username', max_length=100, required=False)
password = forms.CharField(label=u'Entry-specific password', max_length=100, required=False)
This is the view which is used to edit the database entries:
def editentry_page(request):
if request.method == 'POST':
form = EntryEditForm(request.POST)
entryid = unquote(request.POST['entryid'])
if form.is_valid():
entry = request.user.entry_set.get(id=entryid)
entry.title=form.cleaned_data['title'],
entry.username=form.cleaned_data['username'],
entry.password=form.cleaned_data['password'],
entry.targetemail=form.cleaned_data['targetemail'],
entry.user=User.objects.get(username=request.user)
entry.save()
return HttpResponseRedirect('/user/%s/' % request.user.username)
elif 'entryid' in request.GET:
entryid = unquote(request.GET['entryid'])
try:
selectedentry = request.user.entry_set.get(id=entryid)
title = selectedentry.title
targetemail = selectedentry.targetemail
username = selectedentry.username
password = selectedentry.password
except (Entry.DoesNotExist):
raise Http404(u'This entry does not exist or is not your entry')
entry = Entry.objects.get(id=entryid)
form = EntryEditForm({
'title': entry.title,
'email': entry.email,
'target': entry.target,
'username': entry.username,
'password': entry.password,
})
else:
raise Http404(u'No entry selected - return to your entries to try again.')
variables = RequestContext(request, {'form': form, ‘entryid': unquote(request.GET['entryid']),})
return render_to_response('entry_edit.html', variables)
This is the entry model
class Entry(models.Model):
title = models.CharField(max_length=200)
email = models.EmailField(unique=True)
password = models.CharField(max_length=200)
username = models.CharField(max_length=200)
target = models.CharField(unique=False)
user = models.ForeignKey(User)
entry.title=form.cleaned_data['title'],
The final comma is turning it into a tuple in your view. Stop doing that.