Accessing to Odoo data with Django REST API - python

I'm trying to build a Django REST API e-commerce by retrieving data from Odoo, for that I need first of all to connect to odoo database.
Any idea of how to do that !?
PS: with XML-RPC it was easy but I want to to work with REST
Thanks a lot.

Odoo is usually extended internally via modules, but many of its features and all of its data are also available from the outside for external analysis or integration with various tools. Part of the Models API is easily available over XML-RPC and accessible from a variety of languages.
Read more : https://www.odoo.com/documentation/14.0/webservices/odoo.html
Example of XML-RPC by using Django rest framework:
import xmlrpclib
from rest_framework.decorators import api_view
from rest_framework.response import Response
db = '<DB-NAME>'
url = '<your-host-name:8069>'
models = xmlrpclib.ServerProxy('{}/xmlrpc/2/object'.format(url))
common = xmlrpclib.ServerProxy('{}/xmlrpc/2/common'.format(url))
def get_uid():
username = '<your-odoo-username>'
password = '<your-odoo-password>'
uid = common.authenticate(db, username, password, {})
return uid, password
# create default execute method (Odoo Xmlrpc)
def execute(*args):
uid = get_uid()[0]
password = get_uid()[1]
return models.execute_kw(db, uid, password, *args)
# Rest Api for get contact list of odoo with name only
#api_view(['GET'])
def get_contacts_list(request):
contacts = execute('res.partner', 'search_read', [[]], {'fields':['name']})
return Response({'result': contacts, 'status_code': 200})

Related

Redirection in django and passing arguments

I spent some time reading through the documentation and forums, but not sure I understand this. I have this bit of code in the views of my app:
def billboard_index(request):
if request.method == 'POST':
form = SpotiForm(request.POST)
if form.is_valid():
date = form.cleaned_data['spotiread_date']
try:
url = 'https://www.billboard.com/charts/hot-100/' + date
billboard = requests.get(url)
billboard.raise_for_status()
except:
print("No response")
else:
soup = BeautifulSoup(billboard.text, 'html.parser')
positions = [int(i.text) for i in soup.find_all(name='span', class_='chart-element__rank__number')]
songs = [i.text for i in soup.find_all(name='span', class_='chart-element__information__song')]
artists = [i.text for i in soup.find_all(name='span', class_='chart-element__information__artist')]
top100 = list(zip(positions, songs, artists))
if Top100model.objects.exists():
Top100model.objects.all().delete()
for position in top100:
top100data = Top100model(
idtop=str(position[0]), artist=str(position[2]), song=str(position[1])
)
top100data.save()
params = {
'client_id': SPOTIPY_CLIENT_ID,
'response_type': 'code',
'redirect_uri': request.build_absolute_uri(reverse('spotiauth')),
'scope': 'playlist-modify-private'
}
query_string = urlencode(params)
url = '{}?{}'.format('https://accounts.spotify.com/authorize', query_string)
return redirect(to=url)
# if a GET (or any other method) we'll create a blank form
else:
form = SpotiForm()
return render(request, 'billboardapp.html', {'form': form})
on billboard_index I have a form with one field in which user puts a date. This date is then used as an input for a webscraper. I save the scraped data in the database, this works (I know this code will break in couple instances, but I'll deal with this later). Next I want to follow the spotify authorization flow, so I redirect to a url at spotify/authorization, it works. This gives me the code back when I'm redirected to spotiauth.html. At the same time, I print there all the database entries that were added during scraping. This is the spotiauth view:
def spotiauth(request):
Positions100 = Top100model.objects.all()
print(request)
context = {
'positions': Positions100,
}
return render(request, 'spotiauth.html', context=context)
I have couple questions:
How do I pass additional arguments to the spotiauth view? I tried
return redirect(to=url, date=date)
But I can't access it in spotiauth view. So I don't really want to pass it in the url, I just want it as an argument to another function, is this doable?
Is this the actual way to go about it? Not sure this is the simplest thing to do.
Thank you for your help!
Authentication is something that should be handled in a generic way, and not individually and explicitly per request. This is because you don't want to duplicate the authentication code in every request that needs authentication.
Lucky you, you are using Django which already comes with an authentication and authorization layer, and a great community that creates great libraries such as django-allauth that integrate OAuth2 authentication into Django's authentication layer.
OAuth2 against Spotify is what you are trying to implement here. Just
include django-allauth via pip,
configure the Spotify provider in the settings following their documentation,
include their URLs for login and registration (see their docs)
... and you should be able to sign into your app using a Spotify account.
For your regular views then, the decorator login_required would then suffice.
Django-allauth will do the following:
for users who sign in via OAuth2 providers, regular Django accounts will be created automatically
you can see these users in the Django admin, in the same list as the regular Django users
you can manage the configuration of the OAuth2 provider configuration via the Django Admin - django-allauth brings a model with an admin for it
django-allauth brings additional functionality like email verification, multiple email address management etc.
If you want to style the login and registration pages, you can implement your own templates using django-allauth's templates as basis.

JWT and custom authentication in Django Rest Framework

I have written a very basic custom authentication class in order to implement the simple JWT library for my custom authentication needs.
I generate tokens manually and then send the access token to my API. By default, this would be enough but since I do not use the default Django user Model, I get "User not found". This is because I need to implement a custom authentication backend.
I need to read that token in order to query the database with the given user id and check if that token is valid as well. In my example, I have fixed number 2.
class ExampleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
try:
user = vAuthUser.objects.get(pk=2) #this should receive the user_id from the token
except:
raise AuthenticationFailed('No such user')
return (user, None)
My API looks like:
class MyAPI(APIView):
authentication_classes = (ExampleAuthentication,)
permission_classes = (IsAuthenticated ,)
def get()...
Try this:
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth.models import User
user = User.objects.first()
refresh = RefreshToken.for_user(user)
raw_token = str(refresh.access_token)
jwt_a = JWTAuthentication()
validated_token = jwt_a.get_validated_token(raw_token)
repr(validated_token)
print(validated_token["user_id"])
Here I feed a raw access token to get_validated_token() method of JWTAuthentication class. it returns a dictionary with these keys: token_type, jti, user_id, exp, and their associated values.
I've used the default Django User Model for my sample, but it should work with your custom Model.
There are more useful methods like get_header(). check this document and this one too.

Store more than default information in django-rest-framework-jwt

I am using Django version 1.8 and authentication with django-rest-framework-jwt.
After authentication, our application will return to front-end with information:
from rest_framework_jwt.settings import api_settings
from rest_framework.response import Response
from django.contrib.auth.models import User
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
user.profile_picture = "test_profile_picture.jpg" #user is django.contrib.auth.models.User object
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
jwt_login_response = {
"token": token
}
return Response(jwt_login_response,status=status.HTTP_200_OK)
In front-end, we will decode by
import jwtDecode from 'jwt-decode';
var decodedToken = jwtDecode(token);
But at the moment decodedToken only contains value which seem default by django-rest-framework-jwt
Object {username: "test", user_id: 9, email: "test#gmail.com", exp: 1454754137}
I want to store more information in jwt like profile_picture but I don't know how. I really want to find a solution about this problem.
You can override the payload that is attached to the token with the JWT_PAYLOAD_HANDLER setting.
The default implementation includes the username, user_id, email, and exp (expiration time), as you've noticed. You can find the default implementation on GitHub.
I think what you need is a custom user model. You can create this by creating a new Model with the fields you need and reference it to the Default User model via a One-One relationship.
This link might help - http://blog.mathandpencil.com/replacing-django-custom-user-models-in-an-existing-application/

HTTP requests for nested objects

Is it possible to use the library 'requests' (HTTP library for python) to post and update nested objects in django rest framework?
I made a new create method in serializers, but I can't post outside the shell, nor with the requests library or in the api webview.
My Serializers:
class QualityParameterSerializer(serializers.ModelSerializer):
class Meta:
model = QualityParameter
fields = ("id","name", "value")
class ProductQualityMonitorSerializer(serializers.ModelSerializer):
parameters = QualityParameterSerializer(many=True)
class Meta:
model = ProductQualityMonitor
fields = ("id","product_name", "area", "timeslot", "processing_line",
"updated_on",'parameters')
def create(self, validated_data):
params_data = validated_data.pop('parameters')
product = ProductQualityMonitor.objects.create(**validated_data)
for param_data in params_data:
QualityParameter.objects.create(product=product, **param_data)
return product
POST HTTP request
If I may suggest the following form for your serializer:
from django.db import transaction
class ProductQualityMonitorSerializer(serializers.ModelSerializer):
parameters = QualityParameterSerializer(many=True)
class Meta:
model = ProductQualityMonitor
fields = (
"id",
"updated_on",
"product_name",
"area",
"timeslot",
"processing_line",
"parameters",
)
def create(self, validated_data):
# we will use transactions, so that if one of the Paramater objects isn't valid
# that we will rollback even the parent ProductQualityMonitor object creation,
# leaving no dangling objects in the database
params_data = validated_data.pop('parameters')
with transaction.atomic():
product = ProductQualityMonitor.objects.create(**validated_data)
# you can create the objects in a batch, hitting the dB only once
params = [QualityParameter(product=product, **param) for param in params_data]
QualityParameter.objects.bulk_create(params)
return product
About using python requests library: you will have to pay attention to the following aspects when posting to a django back-end:
you must provide a valid CSRF token in your request; the way this is done is via csrf-token cookie;
you must provide the proper authentication headers / tokens / cookies; this is your choice, depends how you're implementing this on the DRF back-end
if this is a request from one domain to another domain, then you have to care for CORS setup.
More to the point: what have you tried already and didn't worked ?

tastypie - where to restrict fields that may be updated by PATCH?

I have a working GET / tastypie (read-only) solution.
I've allowed PUT/PATCH requests and been successful in PATCHING a record.
However I want to limit PATCH capability to only certain fields, on appropriate modelresources, for (already) authenticated and authorised users. I still want users to be able to GET (see) all fields.
Where is the best place (method?) to achieve this sort of restriction?
Docs:
https://django-tastypie.readthedocs.org/en/latest/interacting.html?highlight=patch#partially-updating-an-existing-resource-patch
A bit late but maybe this will help somebody.
My solution was to override update_in_place and check for the data passed.
from tastypie.resources import ModelResource
from tastypie.exceptions import BadRequest
class MyResource(ModelResource):
class Meta:
...
allowed_update_fields = ['field1', 'field2']
def update_in_place(self, request, original_bundle, new_data):
if set(new_data.keys()) - set(self._meta.allowed_update_fields):
raise BadRequest(
'Only update on %s allowed' % ', '.join(
self._meta.allowed_update_fields
)
)
return super(MyResource, self).update_in_place(
request, original_bundle, new_data
)
Since you seem to have authorization for users in place already, you should be able to implement this by adding to the Meta class in your ModelResource. For example, using the DjangoAuthorization (from tastypie docs):
from tastypie.authentication import BasicAuthentication
from tastypie.authorization import DjangoAuthorization
...
class SomeResource(ModelResource):
...
class Meta:
...
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
This example would give you user authorization for actions as defined in django.contrib.auth.models.Permission.
I also had this from the tastypie Google Group. It uses the dehydrate method. Here is the example provided in the Google Groups link:
def dehydrate(self, bundle):
bundle = super(self, MyResource).dehydrate(bundle)
# exclude the restricted field for users w/o the permission foo
if not bundle.request.user.has_perm('app.foo'):
del bundle.data['restricted']
return bundle

Categories

Resources