lookup field in ModelViewSet django rest framework - django

I have an users endpoint in drf.
I want the lookup_field for retrieving user to be something like #username not username.
How can I implement this?

i just solved it by creating a custom router.
in routers.py:
from rest_framework.routers import Route, SimpleRouter
class UserRouter(SimpleRouter):
"""
A router for user to support #username lookup.
"""
routes = [
Route(
url=r'^{prefix}{trailing_slash}$',
mapping={
'get': 'list',
'post': 'create',
},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),
Route(
url=r'^{prefix}/#{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Detail'}
),
]
then in urls.py:
from .routers import UserRouter
from .apis import UserViewSet
from django.urls import path, include
router = UserRouter()
router.register("", UserViewSet, basename="user")
urlpatterns = [
path("/", include(router.urls)),
]
now you can get user with #username lookup for example in my case:
http://127.0.0.1:8000/api/v1/accounts/#mojixcoder/

You can do something like this:
>>> from django.contrib.auth import get_user_model
>>> User = get_user_model()
>>> Users = User.objects.all()
>>> users_list = list()
>>> for user in Users:
... tmp = '#' + str(user)
... users_list.append(tmp)
>>> users_list
['#a', '#admin', '#test23', '#test24']
You can put this code line by line in your view,
if you are using DRF then just return users_list as response
return Response(users_list, {"Message": f"users list"}, status=HTTP_200_OK)
For filtering the users_list on the frontend and making it more interactive;
VanillaJS add event listener to the lookup field. Filter the result.
React, add a two states one for your results users, the other for lookup input,
quick bruteforce search algorthim I can think of is this:
keep in mind this is slow !
handleFindUser = (users, searchedUser) => {
let sr = [];
for(let i = 0; i < users.length; i++){
if(users[i].includes(searchedUser)) {
sr.push( users[i].username, searchedUser)
};
};
}, 1000)
return sr;
};
[EDIT]
Docs
https://www.django-rest-framework.org/api-guide/filtering/
Filter users by url:]
in api/views/
class UsersLookup(generics.ListAPIView):
serializer_class = Users
def get_queryset(self):
"""
This view should return a list of all the purchases for
the user as determined by the username portion of the URL.
"""
username = self.kwargs['username']
return Users.objects.filter(User__username=username)
in api/urls.py
re_path('^users/(?P#<username>.+)/$', UsersLookup.as_view()),

Related

Django redirecting

I can't figure out why my redirection after paying doesn't work.
I'm trying to display the thank_you template once the user has paid via Paypal. My code all works other than the final render of the thank_you template (I receive the email and see the 'Made it' print statement).
Urls:
from django.urls import path
from . import views
app_name = 'checkout'
urlpatterns = [
path('', views.checkout, name='checkout'),
path('thank_you', views.thank_you, name='thank_you'),
path('order_success', views.order_success, name='order_success'),
]
Views.py:
import json
from django.shortcuts import render, redirect, reverse, HttpResponseRedirect
from django.http import JsonResponse
from django.contrib import messages
from django.urls import reverse
from profiles.models import UserProfile
from products.models import Product
from django.views.decorators.http import require_POST
from .models import Order, OrderDetail
from django.core.mail import send_mail
from the_rescuers.settings import DEFAULT_FROM_EMAIL
from templated_email import send_templated_mail
from .forms import OrderForm
def checkout(request):
bag = request.session.get('bag', {})
if not bag:
messages.error(request, "There's nothing in your bag at the moment")
return redirect(reverse('products:products_list'))
order_form = OrderForm()
bag_products = []
for item_id, quantity in bag.items():
product = Product.objects.get(pk=item_id)
name = product.name
id = product.id
bag_products.append({'name': name, 'id': id, 'quantity': quantity})
bag_products = json.dumps(bag_products)
# Attempt to prefill the form with any info the user maintains in
# their profile
if request.user.is_authenticated:
profile = UserProfile.objects.get(user=request.user)
order_form = OrderForm(initial={
'first_name': profile.default_first_name,
'last_name': profile.default_last_name,
'email': profile.default_email,
'phone_number': profile.default_phone_number,
'country': profile.default_country,
'postcode': profile.default_postcode,
'city': profile.default_city,
'street_address_1': profile.default_street_address_1,
'street_address_2': profile.default_street_address_2,
'county': profile.default_county,
})
template = 'checkout/checkout.html'
success_url = '/checkout/order_success'
thank_you = '/checkout/thank_you'
context = {
'order_form': order_form,
'success_url': success_url,
'bag_products': bag_products,
'thank_you': thank_you,
}
return render(request, template, context)
def order_success(request):
"""
View that creates a new object with the JSON data, then redirects to the
thankyou page.
"""
# Take the request, decode it, split it into bag_contents and order_data
# and use this data to create a new order
request2 = request.body
my_json = request2.decode('utf8').replace("'", '"')
json_data = json.loads(my_json)
bag_contents = json_data.get('bagContents')
bag_contents = json.loads(bag_contents)
order_data = json_data.get('jsonData')
order_data = json.loads(order_data)
# Manually fill the user_id field with the user's id
order_data["user_id"] = request.user.id
# Remove the csrf token from the data
order_data.pop("csrfmiddlewaretoken", None)
# Create a new instance of the Order model using the order_data received
order = Order.objects.create(**order_data)
order.save()
# Loop through the bag_contents and save the details in OrderDetail model
for item in bag_contents:
product = Product.objects.get(pk=item['id'])
order_detail = OrderDetail(order=order, product=product,
quantity=item['quantity'])
order_detail.save()
order.update_total()
# Create a value to check in the thank_you view
request.session['redirected_from_order_success'] = True
print("Original: ", request.session)
# Send email to the provided email address
send_templated_mail(
template_name='order_confirmation',
from_email=DEFAULT_FROM_EMAIL,
recipient_list=[order.email],
context={'name': order.first_name,
'order_number': order.order_number,
'order_total': order.order_total,
},
)
return HttpResponseRedirect(reverse('checkout:thank_you'))
def thank_you(request):
"""
View that displays the thankyou page after processing an order.
"""
# Redirect to the custom 404 page if trying to access the page without
# making an order
if request.session.get('redirected_from_order_success'):
# Clear the bag and redirection token now that the order has been
# created
request.session.pop('bag', None)
request.session['redirected_from_order_success'] = False
print("Made it: ", request.session)
return render(request, 'checkout/thank_you.html')
else:
print("Diverted it: ", request.session)
return render(request, "404.html")
Relevant Checkout.html Javascript:
function completeOrder(){
let url = '{{ success_url }}'
const request= fetch(url, {
method: 'POST',
headers:{
'Content-type':'application/json',
'X-CSRFToken': csrftoken,
},
body:JSON.stringify({"bagContents": bagContents, "jsonData": jsonData} )
})
}
onApprove: (data, actions) => {
return actions.order.capture().then(function (orderData) {
const transaction = orderData.purchase_units[0].payments.captures[0];
return completeOrder()})
}
What's confusing is that the GET request for the thank_you template is made and gives a 200, it just doesn't move from the checkout page?
[24/Jan/2023 08:43:02] "POST /checkout/order_success HTTP/1.1" 302 0
Made it: <django.contrib.sessions.backends.db.SessionStore object at 0x7f75867dcb50>
[24/Jan/2023 08:43:03] "GET /checkout/thank_you HTTP/1.1" 200 6287
Any help would be much appreciated!

Django Rest Framework Postman 'POST' request error

I'm still having this error when trying to do a post request from postman.
{
"username": [
"This field is required."
],
"password": [
"This field is required."
]
}
I can make the same post request successfully from my DRF localhost, but when i try on postman i get the error above.
How can I solve it?
Views.py
class PlayThingList(viewsets.ModelViewSet):
serializer_class = PlayThingSerializer
queryset = PlayThing.objects.all()
class UserViewset(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'password']
extra_kwargs = {'password': {
'write_only':True,
'required':True
}}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
Token.objects.create(user)
return user
urls.py
router = DefaultRouter()
router.register('playthings', PlayThingList, basename='playthings')
router.register('users', UserViewset)
urlpatterns = [
path('playmates/', include(router.urls)),
]
Project urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken.views import obtain_auth_token
app_name = 'playthings'
urlpatterns = [
path('admin/', admin.site.urls),
path("", include('playthings.urls')),
path('auth/', obtain_auth_token)
]
UPDATE
I made some changes based on the error messages and guides in the comments and I can now create users.
Problem is, after sending the user credentials in the form, i get this error in postman.
Try adding the following in your POSTMAN
Headers section:
KEY
Value
Accept
application/json
Body section (choose raw or x-www-form-urlencoded):
KEY
Value
username
(your username)
password
(your password)
In your userviewset, you are using UserSerializer. This way you can not create users. To create a user you will have to extend registeruser functionality.
Check out the code from rest-framework and use the same logic in your create method of userviewset. if you want to register a user.
Registeruser is all together a different thing.
Rest framework by default has a url to register users, use that url, it will handle everything for you.
Problem Solved!
The issue was with the token create() method. I changed
create(user) to create(user=user)
ref: serializers.py
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
Token.objects.create(user=user)
return user
Thank you to everyone that helped!

DRF: Simple Switch for a choice field in model via router

I want to build an APIView that can turn on power in a store so to say ...
Can I do it using a router?
model:
class Store(models.Model):
C = [(0,0), (1,1), (2,2), (3,3)]
name = models.IntegerField("name", max_length=60)
power_state = models.PositiveIntegerField("current state", default=0, choices=C)
user = models.ForeignKey(User, on_delete=models.CASCADE)
view:
class OnOff(APIView):
def patch(self, request):
store = Store.objects.get(pk = request.user.id)
return Response("switched")
I am new to DRF and I do not know if I need a serializer here. The interface I see looks like this:
while I was hoping for a simple dropdown between 0 and ... 3 in this case. Also how would the router have to be registered? Right now I put a path in the urls.py:
path('test/', views.OnOff.as_view(), name = "on-off"),
which means it will not be listed under 127.0.0.1:8000/api/ which would be nice.
I tried using (but 404):
router = routers.DefaultRouter()
...
router.register(r'onoff', views.OnOff, basename = "onoff")
urlpatterns = [
path("", views.StoreView.as_view(), name = 'index'),
url('^api/', include(router.urls)),
... ]
The DRF router generates REST style urls and this API does not appear to be REST-ful in the standard sense. ie. list all the objects and detail single objects. docs.
To add the /api/ using the path method:
path('api/test/', views.OnOff.as_view(), name = "on-off"),
if you want to just quickly get you API working via the built in interface, add a post method.
class OnOff(APIView):
def post(self, request):
store = Store.objects.get(pk = request.user.id)
... sudo code to save the on-off value sent from the interface ...
store.power_state = request.data.get('on-off', 0)
store.save()
return Response("switched")
To use the DRF router, a Viewset is required.
The router.url propertyu is list of urls that can be included in the main urls.py
urls.py
from rest_framework.routers import DefaultRouter
import .views
app_name = 'on-off'
router = DefaultRouter()
router.register(r'onoff', views.OnOffViewset)
urlpatterns = router.urls
views.py
class OnOffViewset(viewsets.ViewSet):
def update(self, request, pk=None):
# in this case the pk is a user.id
store = Store.objects.get(pk = request.user.id)
... sudo code to save the on-off value sent from the interface ...
store.power_state = request.data.get('on-off', 0)
store.save()
return Response({'status': store.power_state})
Projects main urls.py
path('api/', include('onoff.urls', namespace='on-off')),

Django Rest Auth - Custom registration logic for social views

I'm building a REST API with Django Rest Framework and Django Rest Auth.
My users have a consumer profile.
class UserConsumerProfile(
SoftDeletableModel,
TimeStampedModel,
UniversallyUniqueIdentifiable,
Userable,
models.Model
):
def __str__(self):
return f'{self.user.email} ({str(self.uuid)})'
As you can see it consists of some mixins that give it a UUID, a timestamp and an updated field and a OneToOne relationship to the user. I use this consumerprofile in relations to link data to the user.
This consumerprofile should get created as soon as a user signs up.
Here is the serializer that I wrote for the registration:
from profiles.models import UserConsumerProfile
from rest_auth.registration.serializers import RegisterSerializer
class CustomRegisterSerializer(RegisterSerializer):
def custom_signup(self, request, user):
profile = UserConsumerProfile.objects.create(user=user)
profile.save()
I connected this serializer in the settings:
REST_AUTH_REGISTER_SERIALIZERS = {
"REGISTER_SERIALIZER": "accounts.api.serializers.CustomRegisterSerializer"
}
It works flawlessly when the users signs up using his email. But when he signs up using facebook, no consumer profile gets created.
I thought the social view would also use the register serializer when creating users? How can I run custom logic after a social sign up?
EDIT for the bounty:
Here are the settings that I use for Django Rest Auth:
# django-allauth configuration
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_ADAPTER = 'accounts.adapter.CustomAccountAdapter'
SOCIALACCOUNT_ADAPTER = 'accounts.adapter.CustomSocialAccountAdapter'
SOCIALACCOUNT_PROVIDERS = {
'facebook': {
'METHOD': 'oauth2',
'SCOPE': ['email', 'public_profile', 'user_friends'],
'AUTH_PARAMS': {'auth_type': 'reauthenticate'},
'INIT_PARAMS': {'cookie': True},
'FIELDS': [
'id',
'email',
'name',
'first_name',
'last_name',
'verified',
'locale',
'timezone',
'link',
'gender',
'updated_time',
],
'EXCHANGE_TOKEN': True,
'LOCALE_FUNC': 'path.to.callable',
'VERIFIED_EMAIL': True,
'VERSION': 'v2.12',
}
}
# django-rest-auth configuration
REST_SESSION_LOGIN = False
OLD_PASSWORD_FIELD_ENABLED = True
REST_AUTH_SERIALIZERS = {
"TOKEN_SERIALIZER": "accounts.api.serializers.TokenSerializer",
"USER_DETAILS_SERIALIZER": "accounts.api.serializers.UserDetailSerializer",
}
REST_AUTH_REGISTER_SERIALIZERS = {
"REGISTER_SERIALIZER": "accounts.api.serializers.CustomRegisterSerializer"
}
And here are the custom adapters (in case they matter):
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.utils import build_absolute_uri
from django.http import HttpResponseRedirect
from django.urls import reverse
class CustomAccountAdapter(DefaultAccountAdapter):
def get_email_confirmation_url(self, request, emailconfirmation):
"""Constructs the email confirmation (activation) url."""
url = reverse(
"accounts:account_confirm_email",
args=[emailconfirmation.key]
)
ret = build_absolute_uri(
request,
url
)
return ret
def get_email_confirmation_redirect_url(self, request):
"""
The URL to return to after successful e-mail confirmation.
"""
url = reverse(
"accounts:email_activation_done"
)
ret = build_absolute_uri(
request,
url
)
return ret
def respond_email_verification_sent(self, request, user):
return HttpResponseRedirect(
reverse('accounts:account_email_verification_sent')
)
class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
def get_connect_redirect_url(self, request, socialaccount):
"""
Returns the default URL to redirect to after successfully
connecting a social account.
"""
assert request.user.is_authenticated
url = reverse('accounts:socialaccount_connections')
return url
Lastly, here are the views:
from allauth.socialaccount.providers.facebook.views import \
FacebookOAuth2Adapter
from rest_auth.registration.views import SocialConnectView, SocialLoginView
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
class FacebookConnect(SocialConnectView):
adapter_class = FacebookOAuth2Adapter
I thought that if I connected the serializer like I did in the initial part of the question the register serializer logic would also get run when someone signs up using facebook.
What do I need to do to have that logic also run when someone signs up using facebook?
(If I can't fix it, I could make a second server request after each facebook sign up on the client side which creates the userconsumerprofile, but that would be kinda overkill and would introduce new code surface which leads to a higher likelihood of bugs.)
Looking briefly at the DefaultAccountAdapter and DefaultSocialAccountAdapter it may be an opportunity for you to override/implement the save_user(..) in your CustomAccountAdapter/CustomSocialAccountAdapter to setup the profile?
Looking just at code it seems that the DefaultSocialAccountAdapter.save_user will finally call the DefaultAccountAdapter.save_user.
Something like this maybe?
class CustomAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=True):
user = super(CustomAccountAdapter, self).save_user(request, user, form,
commit)
UserConsumerProfile.objects.get_or_create(user=user)
return user
There are a few other "hooks"/functions in the adapters that may we worth to investigate if the save_user doesn't work for your scenario.
The REGISTER_SERIALIZER that you created is only used by the RegisterView.
The social login & connect views use different serializers: SocialLoginSerializer and SocialConnectSerializer, that cannot be overwritten per settings.
I can think of two ways to achieve your desired behavior:
create serializers for the social login & connect views (inherriting the default serializers) and set them as serializer_class for the view,
use Django signals, especially the post_save signal for the User model and when an instance is created, create your UserConsumerProfile.

REST REQUEST not returning correct response

I'm fairly new to REST and have been working on using Requests (Python), with Django, and the Django Rest Framework. I am trying to make a get request which returns all objects with a "priority" (Int field) value of 4. The problem is the query doesn't seem to be working and the GET request is returning all project objects. The structure looks correct and I'm able to change the pages using the exact same syntax so I'm not sure what I'm doing wrong. Here is everything I think you'd need, any help would be awesome. Thanks!
Generated URL = http://127.0.0.1:8000/api/projects/?priority=4
RestConnect.py{
local = 'http://localhost/'
base = 'http://127.0.0.1:8000/api/'
def sign_in(current_user, username, password):
response = current_user.get(base + 'api-auth/login/', auth=(username, password))
print response
print response.url
print response.reason
# Some of this is intentionally generic and a little rough (the way the session is being passed)
def find_priority(current_user, username, password):
find_projects = {"priority": "4"}
response = current_user.get(base + 'projects', params=find_projects, auth=(username, password))
print response
print response.url
print response.text
}
QueryTest.py {
import requests
import restconnect
current_user = requests.Session()
restconnect.sign_in(current_user, 'user', 'password')
restconnect.find_priority(current_user, 'user', 'password')
}
serializer.py {
from rest_framework import serializers, viewsets
import django_filters
from . models import Project
class ProjectFilter(django_filters.FilterSet):
class Meta:
model = Project
fields = ['name', 'status', 'priority']
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Project
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
filter_class = ProjectFilter
}