I am trying to store a User's ID and the ID of a Listing in a table. I am new to web development and to me, this seems like a good time to use a ManyToManyField:
class Watchlist(models.Model):
id = models.AutoField(primary_key=True)
user = models.ManyToManyField(User)
listing = models.ManyToManyField(Listing)
When I try to save a new entry in the database, by doing this:
listing_id = self.kwargs['pk']
item = Listing.objects.get(id=listing_id)
user_object = self.request.user
add_to_watchlist = Watchlist(user = user_object, listing = item)
add_to_watchlist.save()
I get the error:
TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use user.set() instead.
I am not sure what I am doing wrong, I have followed the example in the documentation as much as possible.
You can not directly assign a value to a ManyToManyField, but use the .add(…) method [Djang-doc] as the error indicates:
from django.shortcuts import get_object_or_404
item = get_object_or_404(Listing, pk=self.kwargs['pk'])
watchlist = Watchlist.objects.create()
watchlist.user.add(request.user)
watchlist.listing.add(item)
Note: It is often better to use get_object_or_404(…) [Django-doc],
then to use .get(…) [Django-doc] directly. In case the object does not exists,
for example because the user altered the URL themselves, the get_object_or_404(…) will result in returning a HTTP 404 Not Found response, whereas using
.get(…) will result in a HTTP 500 Server Error.
Note: Since a ManyToManyField refers to a collection of elements,
ManyToManyFields are normally given a plural name. You thus might want
to consider renaming user to users.
Related
In my django project, I collect membership data by HTML form and insert them into the database. There are the code samples:
models.py
class member(models.Model):
name = models.CharField(max_length=100,blank=True,null=True)
gender = models.CharField(max_length=10,blank=True,null=True)
profession = models.CharField(max_length=100,blank=True,null=True)
views.py:
def manage(request):
form_values = request.POST.copy()
form_values.pop('csrfmiddlewaretoken') # I don't need it.
add_member = member(**form_values)
add_member.save()
If HTML form input is: Rafi, Male, student
Database gets in list format: ['Rafi'], ['Male'], ['student']
How can I solve this?
You can make use of the .dict() [Django-doc] method here:
def manage(request):
form_values = request.POST.copy()
form_values.pop('csrfmiddlewaretoken')
add_member = member(**form_values.dict())
add_member.save()
If there are multiple values for the same key, it will take the last one.
That being said, it might be better to take a look at a ModelForm [Django-doc] to validate data and convert it to a model object. This basically does what you do here, except with proper validation, removing boilerplate code, and furthermore it will not use the other keys. If here a user would "forge" a POST request with extra key-value pairs, the server will raise a 500 error.
I am trying to make an api backend of something like reddit. I want to ensure that whoever is creating a post (model Post) within a particular subreddit is a member of that subreddit (subreddit model is Sub). Here is my latest effort, which works but seems pretty sloppy, and the serializer for some context.
Post permissions.py
class IsMemberOfSubOrReadOnly(BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
elif request.data:
# prevent creation unless user is member of the sub
post_sub_pk = get_pk_from_link(request.data['sub'])
user = request.user
user_sub_pks = [sub.pk for sub in user.subs.all()]
if not (post_sub_pk in user_sub_pks):
return False
return True
Post serializers.py
from .models import Post
from redditors.models import User
from subs.models import Sub
class PostSerializer(serializers.HyperlinkedModelSerializer):
poster = serializers.HyperlinkedRelatedField(
view_name='user-detail',
#queryset=User.objects.all(),
read_only=True
)
sub = serializers.HyperlinkedRelatedField(
view_name='sub-detail',
queryset=Sub.objects.all()
)
class Meta:
model = Post
fields = ('url', 'id', 'created', 'updated', 'title', 'body',
'upvotes', 'sub', 'poster')
The issue with this approach is that since 'sub' is a hyperlinkedRelatedField on the Post serializer what I get back from request.data['sub'] is just the string hyperlink url. I then have a function, get_pk_from_link that uses regex to read the pk off the end of the url. Then I can use that to grab the actual model I want and check things. It would be nice if there were a more direct way to access the Sub model that is involved in the request.
I have tried searching the fields of the arguments that are available and I can't find a way to get to the Sub object directly. Is there a way to access the Sub model object through the hyperlink url?
I have also solved this problem by just using a serializer field validator (not shown above), but I am interested to know how to do it this way too. Maybe this is just a bad idea and if so please let me know why.
You are right, parsing the url is not the way to go. Since you want to perform the permission check before creating a Post object, I suspect you cannot use object level permissions either, because DRF does not call get_object in a CreateAPIView (since the object does not exist in the database yet).
Considering this is a "business logic" check, a simpler approach would be to not have that permission class at all and perform the check in your perform_create hook in your view (I had asked a similar question about this earlier):
from rest_framework.exceptions import PermissionDenied
# assuming you have a view class like this one for creating Post objects
class PostList(generics.CreateApiView):
# ... other view stuff
def perform_create(self, serializer):
sub = serializer.get('sub') # serializer is already validated so the sub object exists
if not self.request.user.subs.filter(pk=sub.pk).exists():
raise PermissionDenied(detail='Sorry, you are not a member of this sub.')
serializer.save()
This saves you hassle of having to perform that url parsing as the serializer should give you the Sub object directly.
I am trying to call a view using AJAX, but I have a problem. I have a token for the submit, the function that calls to Django view is working, I followed all the instructions of this link: https://realpython.com/blog/python/django-and-ajax-form-submissions/, but in the console I get the following error:
500: DoesNotExist at /Buscar/Producto/
InventarioProducto matching query does not exist.
/Buscar/Producto/ is the URL connected to the view, that is working, I think that is not the problem.
After importing the models, I tried the following in Django shell:
resp_producto=Producto.objects.filter(codigo_producto=9786071411532)
resp_inventario=InventarioProducto.objects.get(producto_codigo_producto__in=resp_producto)
resp_precio=Precio.objects.filter(producto_codigo_producto__in=resp_producto,estado_precio='1').order_by('-idprecio')[:1]
In the shell, if I print the variables were I saved the querysets, I can see the results, so i don't know why this is not working on the view.
9786071411532 is a product code that exist in the MySQL database, it is saved in a column named codigo_producto, which is the same name of a field saved in the model Producto, actually it is a primary key.
Explaining the models:
InventarioProducto has a field that is a foreigin key from Producto. The field in the model InventarioProducto is called producto_codigo_producto, and the primary key of Producto is called codigo_producto. So producto_codigo_producto referes to codigo_producto.
The model Precio has the same foreign key with the same name used in the model InventarioProducto, so they work in the same way.
Also I make sure that all the data that I'm requesting really exists.
Here is the view:
def BuscarProducto(request):
if request.method == 'POST':
txt_codigo_producto = request.POST.get('id_codigo_producto')
response_data = {}
resp_producto=Producto.objects.filter(codigo_producto=txt_codigo_producto)
resp_inventario=InventarioProducto.objects.get(producto_codigo_producto__in=resp_producto)
resp_precio=Precio.objects.filter(producto_codigo_producto__in=resp_producto,estado_precio='1').order_by('-idprecio')[:1]
response_data['result'] = 'Create post successful!'
response_data['codigoproducto'] = resp_producto.codigoproducto
response_data['lote'] = resp_inventario.idinventario_producto
response_data['descripcion_producto'] = resp_producto.descripcion_producto
response_data['precio'] = resp_precio.valor_precio
return HttpResponse(
json.dumps(response_data),
content_type="application/json"
)
else:
return HttpResponse(
json.dumps({"nothing to see": "this isn't happening"}),
content_type="application/json"
)
I've moved the SOLVED text to an answer:
I changed this:
resp_inventario=InventarioProducto.objects.get(producto_codigo_producto__in=resp_producto)
To this:
resp_inventario=InventarioProducto.objects.filter(producto_codigo_producto__in=resp_producto)
When I redirect to another view after adding data, specifically stripe Customer data, to a dict that is then added to my session, I lose all of the information in my session at the redirected view. Thus, I encounter a KeyError when I try to pop these items.
Interestingly, this does not happen when I put other types of information in my payment_data dict, like a list instead of a customer object.
I'm not sure what's the best way to fix this problem, but given what I have designed, it's important for me to get the Customer information to the confirm view so that I can
List item
Display customer information to the user for confirmation (censoring sensitive information
Charge the card
This is my code:
class PaymentsCreateView(FormView):
def form_valid(self, form):
customer = stripe.Customer.create(description="""
Non-registered user for applying features""")
customer.save()
payment_data = {
'customer': customer
}
self.request.session['payment_data'] = payment_data
self.request.session.modified = True
import ipdb;ipdb.set_trace();
return HttpResponseRedirect(reverse('payments_confirm'))
class PaymentsConfirmView(TemplateView):
template_name = 'payments/confirm.html'
def get_context_data(self, **kwargs):
context = super(PaymentsConfirmView, self).get_context_data(**kwargs)
context['payment_data'] = self.request.session.pop('payment_data')
context['feature_data'] = self.request.session.pop('feature_data')
return context
I'm still debugging and my next step is to confirm whether the issue is that I am trying to store a Customer object rather than a dictionary or list object but maybe someone on SO can confirm or supply the right answer.
From the Python docs:
list.pop([i])
Remove the item at the given position in the list, and return it. If no index is specified, a.pop() removes and returns the last item in the list.
Like Rohan says, use get():
context['payment_data'] = self.request.session.get('payment_data', False)
context['feature_data'] = self.request.session.get('feature_data', False)
I don't really understand get_comment_permalink in Django's comments framework.
I created a few comments for my class Order using Django's comments, by default it shows a url of something like /comments/cr/18/1/#c1 and that url never exists.
I looked at the comment's urls.py and it has a line that says
urlpatterns += patterns('',
url(r'^cr/(\d+)/(.+)/$', 'django.contrib.contenttypes.views.shortcut', name='comments-url-redirect'),
)
The views.py which has the shortcut method is
from django import http
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site, get_current_site
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import ugettext as _
def shortcut(request, content_type_id, object_id):
"""
Redirect to an object's page based on a content-type ID and an object ID.
"""
# Look up the object, making sure it's got a get_absolute_url() function.
try:
content_type = ContentType.objects.get(pk=content_type_id)
if not content_type.model_class():
raise http.Http404(_(u"Content type %(ct_id)s object has no associated model") %
{'ct_id': content_type_id})
obj = content_type.get_object_for_this_type(pk=object_id)
except (ObjectDoesNotExist, ValueError):
raise http.Http404(_(u"Content type %(ct_id)s object %(obj_id)s doesn't exist") %
{'ct_id': content_type_id, 'obj_id': object_id})
try:
get_absolute_url = obj.get_absolute_url
except AttributeError:
raise http.Http404(_("%(ct_name)s objects don't have a get_absolute_url() method") %
{'ct_name': content_type.name})
absurl = get_absolute_url()
# Try to figure out the object's domain, so we can do a cross-site redirect
# if necessary.
# If the object actually defines a domain, we're done.
if absurl.startswith('http://') or absurl.startswith('https://'):
return http.HttpResponseRedirect(absurl)
# Otherwise, we need to introspect the object's relationships for a
# relation to the Site object
object_domain = None
if Site._meta.installed:
opts = obj._meta
# First, look for an many-to-many relationship to Site.
for field in opts.many_to_many:
if field.rel.to is Site:
try:
# Caveat: In the case of multiple related Sites, this just
# selects the *first* one, which is arbitrary.
object_domain = getattr(obj, field.name).all()[0].domain
except IndexError:
pass
if object_domain is not None:
break
# Next, look for a many-to-one relationship to Site.
if object_domain is None:
for field in obj._meta.fields:
if field.rel and field.rel.to is Site:
try:
object_domain = getattr(obj, field.name).domain
except Site.DoesNotExist:
pass
if object_domain is not None:
break
# Fall back to the current site (if possible).
if object_domain is None:
try:
object_domain = get_current_site(request).domain
except Site.DoesNotExist:
pass
# If all that malarkey found an object domain, use it. Otherwise, fall back
# to whatever get_absolute_url() returned.
if object_domain is not None:
protocol = request.is_secure() and 'https' or 'http'
return http.HttpResponseRedirect('%s://%s%s'
% (protocol, object_domain, absurl))
else:
return http.HttpResponseRedirect(absurl)
and it's too complicated for me to understand.
When Django says permalink, I think about having a reference to a particular place on a page (usually a header). For example, the Django's comments framework documentation is link #1, and you can permalink the "Linking to Comments" section with link #2.
1. https://docs.djangoproject.com/en/dev/ref/contrib/comments/
2. https://docs.djangoproject.com/en/dev/ref/contrib/comments/#linking-to-comments
So for the comments, shouldn't it be the same? Shouldn't the URL simply be a #c1 or something without the /comments/cr/18/1/...? In fact I don't even know where Django got 18 and 1... From the shortcut method, I understand that 18 is the content_type_id and 1 is the object_id, but how can I tell which class in models.py is which content type id and object id?
The comments framework uses Generic Relations to link Comment objects to your database objects (Order model in your case). Generic relationships allow one object to maintain a relationship with another object without explicitly knowing about it's class. You can see the fields creating the generic relationship (content_type, object_pk, content_object) for comment here: django.contrib.comments.models
Once a comment has been made and attached to an instance of a particular class (a single Order for example), we need a way to get a link to that particular comment (the permalink). To get a link to a comment, we need to know the URL of the object the comment has been made on (again, the particular Order in your case). This is what get_comment_permalink is doing - it constructs a URL to the object for which a comment has been left on and also attached an anchor link (the #c1 part) to the URL so that the browser jumps to a particular comment on that page.
To do all this it has 3 steps:
first figure out what type of object it's dealing with by looking up the generic relationship. This will leave us with a Order object
Now it tries to get the absolute url get_absolute_url of that object. This might be /order/my-order/
It constructs that `http://mysite.com/' part of the URL by using the Sites framework
It figures out the #c31 (anchor link to the comment) part of the url
Now we have a full http://mysite.com/order/my-order/c#31 that will bring us to the correct page and show the correct comment
So for the comments, shouldn't it be the same? Shouldn't the URL simply be a #c1 or something without the /comments/cr/18/1/...? In fact I don't even know where Django got 18 and 1... From the shortcut method, I understand that 18 is the content_type_id and 1 is the
18 is the content type id, and 1 is the object id. The shortcut view fetches the object from the database using these parameters and redirects to modelobject.get_absolute_url().
Define/fix get_absolute_url() method in your models, this will repair django.contrib.contenttypes.views.shortcut.
That said, it is expected by Django that the url of the model object displays the list of comments for this object. In that case, just add <a name="c{{ comment.id }}"></a> in your single comment HTML.