Pass django model query as json response - python

I'm working on a social network. I want to load the comments of each post so I make an API call to the server to fetch all the comments of the required posts. The code will make everything clear:
urls.py
path("comments/<int:post_id>", views.load_comments)
models.py
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
commented_by = models.ForeignKey(User, on_delete=models.CASCADE)
comment = models.CharField(max_length=128)
views.py
def load_comments(request, post_id):
"""Returns the comments to index.js"""
try:
# Filter comments returned based on post id
post = Post.objects.get(pk=post_id)
comments = list(Comment.objects.filter(post=post).values())
return JsonResponse({"comments": comments})
except:
return JsonResponse({"error": "Post not found", "status": 404})
index.js
fetch(`/comments/${post_id}`)
.then(res => res.json())
.then(res => {
// Appoints the number of comments in the Modal title
document.querySelector("#exampleModalLongTitle").textContent = `${res.comments.length} Comments`;
res.comments.forEach(comment => {
modal_body = document.querySelector(".modal-body")
b = document.createElement("b");
span = document.createElement("span");
br = document.createElement("br");
span.classList.add("gray")
b.textContent = comment.commented_by_id + " ";
span.textContent = comment.comment;
modal_body.appendChild(b);
modal_body.appendChild(span);
modal_body.appendChild(br);
})
I can get the comment value using comment.comment in js. However, the problem arises when I convert the comments objects to list.values so now I lose the ability to get the user who posted the comment (commented_by)
Any help to get the comment and commented_by values is appreciated.
I also tried in views.py:
def load_comments(request, post_id):
"""Returns the comments to index.js"""
try:
# Filter comments returned based on post id
post = Post.objects.get(pk=post_id)
post_comments = Comment.objects.filter(post=post)
comments = []
for comment in post_comments:
comments.append({
"comment": comment.comment,
"commented_by": commented_by,
})
comments = serializers.serialize('json', comments)
return HttpResponse(comments, content_type='application/json')
except:
return JsonResponse({"error": "Post not found", "status": 404})
However when I try this, it outputs the error.

In Django, the values() function for a ForeignKey field by default returns the id of the related object with the key formed by appending the name of the field with "_id". So in your case, you have your user ID under the key commented_by_id.
If by "loosing the ability to get the user who posted the comment" you mean other user info, like username, then you can pass the fields that you need to the values() function.
comments = list(Comment.objects
.filter(post=post)
.values("comment",
"commented_by__username"
))
will give a dict with the comment text and the user name (assuming that you have a username field in your User model.

1- you should use django_Rest_framework cuz it's very easy to work with api and show related field
2- use Post.objects.get(id=post_id) instead of pk
or you should skip this step by filter the comments depend on post id directly likeComment.objects.all().filter(post_id=post_id)

Related

cannot retrieve values from database django

I have a table I am trying to retrieve values from. It has two foreign keys and that is it.
I use get_or_create on the table and create an object. I verify with the admin panel that the object is there. Then, I try to retrieve it but I do not get the values even though I can see them in the admin panel.
Here is the table:
class req(models.Model):
to = models.ForeignKey(User,on_delete=models.CASCADE)
from = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return "request sent"
In the piece of code below, I am retrieving from the database and I try to display it as it is in the p tag(html). In the template, I tried {{ req.to }} and {{ req.from.first_name }} but in vain, I did not see any values when html is rendered.
def get(self, request):
u = get_user(request)
try:
my_requests = req.objects.get(from=u)
except req.DoesNotExist:
fr = None
if fr == None:
return redirect(reverse('myapp:index'))
if my_requests != False:
context_dict['req'] = my_requests
return render(request, 'myapp/req.html', context=context_dict)
Can anybody see the problem that I cannot and comment ?
There are couple of issues in your code. here is the updated version which address your issue :-
def get(self, request):
req_obj, created = req.objects.get_or_create(from=request.user)
if created:
context_dict['req'] = my_requests
return render(request, 'myapp/req.html', context=context_dict)
return redirect(reverse('myapp:index'))

How can you customize the response message of the post in django?

I am trying to make a membership API using django rest frameworks.I made a code and checked that the function was working properly. In my current code, if the data of the email, password, and username are empty, the message is given as follows.
{
"email": [
"This field is required."
],
"username": [
"This field is required."
],
}
But after talking about this with my team's client developers, they said it was better to give a unified message as below.
{
"message": "email field is required."
}
How can I customize the value like this? Here's my code.
class customSignUpView (GenericAPIView) :
serializer_class = customRegisterSerializer
def post (self, request) :
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user = User.objects.get(email=serializer.data['email'])
token = RefreshToken.for_user(user).access_token
current_site = get_current_site(request).domain
relativeLink = reverse('emailVerify')
absurls = F'http://{current_site}{relativeLink}?token={token}'
email_body = F'Hi {user.username} Use link below to verify your email \n{absurls}'
data = {'email_body': email_body, 'to_email': user.email, 'email_subject': 'Verify your email'}
Util.send_email(data)
return Response({'message': 'check your email.'}, status=201)
you need to customize customRegisterSerializer further by adding a custome validate method to it, just try to do something like this
class YourSerializer(serializers.Serializer):
field1 = serializers.CharField(args)
...
fieldn = serializers.CharField(args)
def validate(self, data):
error = {}
if 'some_field' in data:
test field is valid here
if data['some_field'] is not valid:
error['some_field'] = 'your message'
.... ad nauseam
if error:
raise serializers.ValidationError(error)
return data
pass the arguments as the data parameter, and you should be able to customize everything however you want
First of all would like to say that the standard DRF approach to error messages is more universal, as it allows sending several messages for several fields in a unified way. I.e. that in the returned JSON key is the field name and value - the list of messages. Which also allows FE to display the messages next to the appropriate field.
Cause with the format you're trying to achieve comes the question of what kind of message to send if both email and username were not provided but are required (for e.g. should it be one message string or a list of "{field_name} is required" strings?).
But if you really need to achieve the approach you mentioned, let me elaborate on the answer by #vencaslac. So in your case the serializer should roughly look like:
class CustomRegisterSerializer(serializers.ModelSerializer):
...
def validate(self, data):
if not data.get("email"):
raise serializers.ValidationError({"message": "email field is required."})
return data
The validate() method is the same for both Serializer and ModelSerializer. You can find more info in the docs. But again, with this approach you need to figure out an answer to the question I mentioned above.

Validate POST request (Vue + DRF)

So here's expected flow of the request:
The user creates a new Language via an html form. [√ ]
Vue via axios will fire a POST request to drf. [√ ]
Drf will validate the data from the POST request (see if the language name/id/etc already exists) [x]
Create the new language if it passes the validation. [x]
I'm currently stuck on #3.
I tried putting this on my LanguageViewSet:
def post(self, request):
language = request.data.get('language')
serializer = LanguageSerializer(data=language)
if serializer.is_valid(raise_exception=True):
language_saved = serializer.save()
return Response({"success": "Language '{}' created successfully!".format(language_saved.name)})
However, this doesn't somewhat work and gets completely ignored since:
I tried commenting the post function, but still if I call a POST request via axios on the LanguageViewSet it would still post. probably a built-in POST feature?
If the function is there, notice I used language = request.data.get('language') which means on my axios, the name of my data to be sent should be language right? otherwise it would ignore the POST request. I used created_lang in axios, fired the POST req but still it posted without any errors as if it completely ignored my post function.
If I tried posting a new language of which it's name is already registered on the database, it would still create it making duplicate records.
Forgive my naiveness I am completely new to drf and django :<
Here's my codes:
Language model:
class Language(models.Model):
name = models.CharField(max_length=100, default='New Language')
def __str__(self):
return self.name
Its serializer:
class LanguageSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Language
fields = ['id', 'name']
view:
class LanguageViewSet(viewsets.ModelViewSet):
queryset = Language.objects.all().order_by('name')
serializer_class = LanguageSerializer
def get_queryset(self):
queryset = Language.objects.all().order_by('name')
lang = self.request.query_params.get('lang','')
if lang:
return Language.objects.filter(pk=lang)
else:
return queryset
and url:
router = routers.DefaultRouter()
router.register(r'languages', views.LanguageViewSet)
On my frontend, here's my form:
<form #submit.prevent="PostLanguage" class="mt-3" action='' method="POST">
<input type="text" v-model="created_lang.name" name="name" id="name" placeholder="Language Name" autocomplete="off" required>
<input type="submit" value="Post">
</form>
And will be posted by this simple Vue script:
PostLanguage(){
let params = Object.assign({}, this.created_lang)
axios.post(
'http://127.0.0.1:8000/api/languages/', params
)
.then(response=>{
console.log(response)
this.GetLanguages()
this.created_lang.name = ''
})
.catch(error => {
console.log(error)
})
}
Update
I tried this:
class LanguageSerializer(serializers.ModelSerializer):
class Meta:
model = Language
fields = ['id', 'name']
def validate_name(self, value):
existed_language = Language.objects.filter(name=value).get()
if existed_language.name == value:
return Response(status=400)
else:
return value
if the name data from the POST is new (it's not used in the database) it would then return the value of it thus creating a new language. However if it already exists, I tried returning a response but it would create a language with its name = <Response status_code=400, "text/html; charset=utf-8">. I know it's kinda funny but I think this is a step to the right direction.
If language is unique in your model then add unique true in your model definition.
class Language(models.Model):
name = models.CharField(max_length=100, default='New Language', unique=True)
def __str__(self):
return self.name
This will cause serializer.validate to fail and won't create duplicate languages.
Update
The validate_name in your serializer returns the value of the filed after executing the validation logic. So you can update it with raise serializers.ValidationError("Language already exist") instead of Response (400) statement.

How to pass hidden variable to a form in django so it could be validated?

I have a registration form that goes through all the usual stuff, but with one bot prevention thing. I made a model SecurityQuestion, that consists of two charfields, Question and Answer. During registration one of them is randomly picked and is supposed to pass an answer to the form so it can be validated there. However, for the reason I'm yet to figure out, it doesn't seem to be passing the answer to the form
So let's start with the code
profile/forms.py
# FORM: Register an account
class UserRegistrationFirstForm(forms.ModelForm):
username = forms.CharField(max_length=20)
password = forms.CharField(widget=forms.PasswordInput)
password_confirm = forms.CharField(widget=forms.PasswordInput)
email = forms.EmailField()
email_confirm = forms.EmailField()
answer = forms.CharField(max_length=50, required=True)
hidden_answer = forms.CharField(widget=forms.HiddenInput)
class Meta:
model = User
fields = [
'username',
'email',
'email_confirm',
'password',
'password_confirm',
'answer',
]
def clean_answer(self):
formated_user_answer = self.cleaned_data.get("answer").lower()
formated_hidden_answer = self.cleaned_data.get("hidden_answer").lower()
if formated_hidden_answer != formated_user_answer:
raise forms.ValidationError("Incorect answer to security question!")
return answer
As you can see, there are two fields, answer and hidden_answer. answer is where users types in their answers, and hidden_answer is supposed to be populated when initializing form and passing init.
profiles/views.py
# VIEW: Register an account
def custom_register(request):
if request.user.is_authenticated():
return redirect(reverse('profile', host='profiles'))
# Grab a random registration security Q&A
qa_count = SecurityQuestion.objects.count() - 1
sec_qa = SecurityQuestion.objects.all()[randint(0, qa_count)]
# Form for users to register an account
form = UserRegistrationForm(request.POST or None, initial={"hidden_answer": sec_qa.answer,})
# Validate the registration form
if form.is_valid():
# Create new account
user = form.save(commit=False)
password = form.cleaned_data.get("password")
user.set_password(password)
user.save()
# Set the default avatar
user.profile.avatar = get_default_avatar()
user.profile.save()
login(request, user)
messages.success(request, "Welcome " + user.username + ", you have successfully registered an account!")
return redirect(reverse('pages:frontpage', host='www'))
# Context dict to return for template
context = {
"title": "Registration",
"form": form,
"question": sec_qa.question,
}
return render(request, 'profiles/register.html', context)
Alright, so in registration view, I randomly pick one of the security questions and then pass it to the form with initial={"hidden_answer": sec_qa.answer,}. However it doesn't seem the be going through, as I'm getting following error:
'NoneType' object has no attribute 'lower'
Exception Location: path/to/profiles/forms.py in clean_answer, line 103
formated_hidden_answer = self.cleaned_data.get("hidden_answer").lower()
OK, so NoneType would mean there's nothing to reference to. I've tried couple different ways to fix this. I tried putting hidden_answer in form's meta field list. I also tried {{ form.hidden_answer.as_hidden }} in the template (which is complete opposite of what I'm trying to achieve here, as the answer is still displayed in value of that hidden input in the page source). Any idea what am I doing wrong with this?
EDIT: If there's an alternative or a simple solution to what I'm trying to do, could you please reference any documentation about it?
Sending a hidden input can't prevent a user from knowing the hidden_answer. It won't be visible in browser but will very well be present in your DOM and accessible to any user. Sending the answer(hidden or not) to the client side is itself a flaw in security.
You should only send the question to the client side(browser) and later verify it in you clean() method.
If I understand you use case correctly(correct me if I'm wrong), you should do something like:
In your views.py, do something like:
def custom_register(request):
if request.user.is_authenticated():
return redirect(reverse('profile', host='profiles'))
if request.method == 'GET':
# Grab a random registration security Q&A
qa_count = SecurityQuestion.objects.count() - 1
sec_qa = SecurityQuestion.objects.all()[randint(0, qa_count)]
#Give the text of your question to sec_qa_title. Do something like the following.
#sec_qa_title = sec_qa.title
#sec_qa_title should now have the question string of the SecurityQuestion model object instance.
form = UserRegistrationForm(initial={'question' : sec_qa_title})
#initialize whatever context variables you want.
#Rest of your code.
#return a suitable response which will display you form with the security question.
#return render(request, 'profiles/register.html', context)
if request.method == 'POST':
#All the data of form submitted by the user is inside request.POST
form = UserRegistrationForm(request.POST)
# Validate the registration form
if form.is_valid():
#Do your stuff. Return a suitable response.
else:
#Do your stuff. Return a suitable response.
Now in your forms.py, do something like:
class UserRegistrationFirstForm(forms.ModelForm):
username = forms.CharField(max_length=20)
password = forms.CharField(widget=forms.PasswordInput)
password_confirm = forms.CharField(widget=forms.PasswordInput)
email = forms.EmailField()
email_confirm = forms.EmailField()
question = forms.CharField(max_length=50, required=True)
#removed hidden_answer field and added a question field.
answer = forms.CharField(max_length=50, required=True)
class Meta:
model = User
fields = [
'username',
'email',
'email_confirm',
'password',
'password_confirm',
#Remove the answer field.
]
def clean_answer(self):
security_question_title = self.cleaned_data.get("question")
#get the question title.
formatted_user_answer = self.cleaned_data.get("answer").lower()
#now get the SecurityQuestion model.
try:
sec_qa = SecurityQuestion.objects.get(title = security_question_title)
#Don't forget to import SecurityQuestion model.
except SecurityQuestion.DoesNotExist:
#If a user changes the question, you don't want it to fiddle with you system.
raise forms.ValidationError("Question was changed. Wrong practice.")
#Finally check the answer.
if formatted_user_answer != sec_qa.answer.lower():
raise forms.ValidationError("Incorrect answer to security question!")
return answer
There are many improvements that you can later try.
For example: Sending a question and an id with it to later extract the question through that id(instead of extracting it from the whole string; slightly unreliable)
I hope you understand the flow and construct it correctly.
There may be some errors since I didn't test the code but I hope you'll fix them.
I hope this guides you in some way. Thanks.

Passing form data from my view to thank you page

Is there a way I can pass data from a form submission over to the 'thank you' page. The reason i'd like to do this is because I have a form on the website, where the user will select multiple fields which all contains different PDF's.
So once the user has submitted the form the idea is to re-direct them to a thankyou page, where they can view the list of pdf/files they have selected on the form.
I hope this is enough info to go on. Here are my views / models.
def document_request(request, *args):
# template = kwargs['name'] + ".html"
if request.method == 'POST':
form = forms.ReportEnquiryForm(request.POST)
print(request.POST)
if form.is_valid():
docrequest = form.save()
return HttpResponseRedirect(reverse('thank_you', kwargs={'id': docrequest.id}))
else:
form = forms.ReportEnquiryForm()
return render_to_response('test.html',{'form':form})
def thank_you(request):
docrequest = DocumentRequest.objects.get(pk=id)
return render_to_response('thankyou.html',
{'docrequest' : docrequest },
context_instance=RequestContext(request))
My initial idea was to pass the data to a new view called thank_you. But not this is possible.
class DocumentUpload(models.Model):
name = models.CharField(max_length="200")
document_upload = models.FileField(upload_to="uploads/documents")
def __unicode__(self):
return "%s" % self.name
class DocumentRequest(models.Model):
name = models.CharField(max_length="200")
company = models.CharField(max_length="200")
job_title = models.CharField(max_length="200")
email = models.EmailField(max_length="200")
report = models.ManyToManyField(DocumentUpload)
def __unicode__(self):
return "%s" % self.name
form.py
class ReportEnquiryForm(forms.ModelForm):
class Meta:
model = models.DocumentRequest
fields = ('name', 'company', 'job_title', 'email', 'report')
If you need anymore info, please ask :)
You've saved the user's submission in a DocumentRequest object. So you can pass the ID of that object in the URL when you redirect, and in the thank_you view you can get the DocumentRequest and render the list.
Edit The idea is to make the thank_you page like any other view that accepts a parameter from the URL:
url(r'thanks/(?P<id>\d+)/$, 'thank_you', name='thank_you')
and so the POST part of the form view becomes:
if form.is_valid():
docrequest = form.save()
return HttpResponseRedirect(reverse('thank_you', kwargs={'id': docrequest.id}))
and thank_you is:
def thank_you(request, id):
docrequest = DocumentRequest.objects.get(pk=id)
return render_to_response('thankyou.html',
{'docrequest' : docrequest },
context_instance=RequestContext(request))
Second edit
As others have suggested, this makes it possible for anyone to see the request. So a better solution is to put it in the session:
docrequest = form.save()
request.session['docrequest_id'] = docrequest.id
and in thank_you:
def thank_you(request):
if not 'docrequest_id' in request.session:
return HttpResponseForbidden
docrequest = DocumentRequest.objects.get(request.session['docrequest_id'])
You can do as Daniel Roseman said but in this case the thank you pages can be accessed by anyone with the Ids.
Some ways to pass data between views are the following(the list is not mine.):
GET request - First request hits view1->send data to browser -> browser redirects to view2
POST request - (as you suggested) Same flow as above but is suitable when more data is involved
Using django session variables - This is the simplest to implement
Using client-side cookies - Can be used but there is limitations of how much data can be stored.
Maybe using some shared memory at web server level- Tricky but can be done.
Write data into a file & then the next view can read from that file.
If you can have a stand-alone server, then that server can REST API's to invoke views.
Again if a stand-alone server is possible maybe even message queues would work.
Maybe a cache like memcached can act as mediator. But then if one is going this route, its better to use Django sessions as it hides a whole lot of implementation details.
Lastly, as an extension to point 6, instead of files store data in some persistent storage mechanism like mysql.
The simplest way is to use sessions. Just add the id to the session and redirect to the thank you view, you read the id value and query the db with that id.

Categories

Resources