-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
1,100 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from __future__ import print_function, unicode_literals | ||
from sugarcrm import Task | ||
|
||
# This is the URL for the v4 REST API in your SugarCRM server. | ||
# url = '' | ||
# username = '' | ||
# password = '' | ||
|
||
# This way you log-in to your SugarCRM instance. | ||
# conn = Sugarcrm(url, username, password) | ||
# TaskModel = Task(conn) | ||
|
||
|
||
# Examples for Django | ||
# You need to put connection settings to the Django settings file first. | ||
# SUGAR_CRM_URL = <sugarcrm api url> | ||
# SUGAR_CRM_USERNAME = <sugarcrm username> | ||
# SUGAR_CRM_PASSWORD = <sugarcrm password> | ||
|
||
# creation of new Tasks | ||
new_task = Task(**{'name': 'test'}) | ||
new_task.save() | ||
|
||
# change field value | ||
TaskModel = Task() | ||
task = TaskModel.objects.get(pk=new_task.id) | ||
old_name = task.name | ||
print('before change', task.name) | ||
task['name'] = 'New Name' | ||
print('before save', task.name) | ||
task.save() | ||
|
||
# revert field value | ||
query = TaskModel.objects.filter(id=new_task.id).only('id', 'name', 'date_start').order_by('date_start') | ||
task = query.first() | ||
print('after save', task.name) | ||
task['name'] = old_name | ||
task.save() | ||
query = TaskModel.objects.filter(id=new_task.id).only('id', 'name', 'date_start').order_by('date_start') | ||
task = query.first() | ||
print('after revert', task.name) | ||
task.delete() | ||
|
||
query = TaskModel.objects.filter(name=new_task.name).order_by('date_start') | ||
task = query.first() | ||
print('check after delete', task) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from setuptools import setup, find_packages | ||
|
||
NAME = 'django-sugar-crm' | ||
VERSION = '0.0.1' | ||
PACKAGES = find_packages() | ||
AUTHOR = 'an42rus' | ||
URL = f'https://github.com/{AUTHOR}/{NAME}' | ||
|
||
setup( | ||
name=NAME, | ||
version=VERSION, | ||
packages=PACKAGES, | ||
author=AUTHOR, | ||
url=URL, | ||
description='SugarCRM Python library with object api similar to Django ORM.' | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .sugarcrm import * | ||
from .sugarentry import * | ||
from .rest_framework import * | ||
|
||
__version__ = "0.0.1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .helpers import * | ||
from .filterset import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from django_filters.rest_framework import filterset | ||
|
||
|
||
class FilterSet(filterset.FilterSet): | ||
def filter_queryset(self, queryset): | ||
for name, value in self.form.cleaned_data.items(): | ||
queryset = self.filters[name].filter(queryset, value) | ||
return queryset |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
class Field: | ||
""" | ||
Class needs to support DRF filtering system | ||
""" | ||
def __init__(self, name): | ||
self.name = name | ||
self.verbose_name = self.name.replace('_', ' ') | ||
|
||
|
||
class Meta: | ||
""" | ||
Class need to support DRF views | ||
""" | ||
def __init__(self, object_name): | ||
self.object_name = object_name | ||
|
||
def get_field(self, name): | ||
""" | ||
method needs to support DRF filtering system | ||
""" | ||
return Field(name) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
try: | ||
from django.conf import settings | ||
except: | ||
settings = None | ||
|
||
API_URL = getattr(settings, 'SUGAR_CRM_URL', '') | ||
USERNAME = getattr(settings, 'SUGAR_CRM_USERNAME', '') | ||
PASSWORD = getattr(settings, 'SUGAR_CRM_PASSWORD', '') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
from six.moves import urllib | ||
import hashlib | ||
import json | ||
|
||
from .sugarerror import SugarError, SugarUnhandledException, is_error | ||
from .settings import API_URL, USERNAME, PASSWORD | ||
|
||
|
||
class Sugarcrm: | ||
"""Sugarcrm main interface class. | ||
This class is what is used to connect to and interact with the SugarCRM | ||
server. | ||
""" | ||
|
||
def __init__(self, url, username, password, is_ldap_member=False): | ||
"""Constructor for Sugarcrm connection. | ||
Keyword arguments: | ||
url -- string URL of the sugarcrm REST API | ||
username -- username to allow login upon construction | ||
password -- password to allow login upon construction | ||
""" | ||
|
||
# String which holds the session id of the connection, required at | ||
# every call after 'login'. | ||
self._session = "" | ||
|
||
# url which is is called every time a request is made. | ||
self._url = url | ||
|
||
self._username = username | ||
self._password = password | ||
self._isldap = is_ldap_member | ||
|
||
# Attempt to login. | ||
self._login() | ||
|
||
# Dynamically add the API methods to the object. | ||
for method in ['get_user_id', 'get_user_team_id', | ||
'get_available_modules', 'get_module_fields', | ||
'get_entries_count', 'get_entry', 'get_entries', | ||
'get_entry_list', 'set_entry', 'set_entries', | ||
'set_relationship', 'set_relationships', | ||
'get_relationships', 'get_server_info', | ||
'set_note_attachment', 'get_note_attachment', | ||
'set_document_revision', 'get_document_revision', | ||
'search_by_module', 'get_report_entries', 'logout']: | ||
# Use this to be able to evaluate "method". | ||
def gen(method_name): | ||
def f(*args): | ||
try: | ||
result = self._sendRequest(method_name, | ||
[self._session] + list(args)) | ||
except SugarError as error: | ||
if error.is_invalid_session: | ||
# Try to recover if session ID was lost | ||
self._login() | ||
result = self._sendRequest(method_name, | ||
[self._session] + list(args)) | ||
elif error.is_missing_module: | ||
return None | ||
elif error.is_null_response: | ||
return None | ||
elif error.is_invalid_request: | ||
print(method_name, args) | ||
else: | ||
raise SugarUnhandledException('%d, %s - %s' % | ||
(error.number, | ||
error.name, | ||
error.description)) | ||
|
||
return result | ||
|
||
f.__name__ = method_name | ||
return f | ||
|
||
self.__dict__[method] = gen(method) | ||
|
||
# Add modules containers | ||
self.modules = {} | ||
self.rst_modules = dict((m['module_key'], m) | ||
for m in self.get_available_modules()['modules']) | ||
|
||
def __getitem__(self, key): | ||
if key not in self.rst_modules: | ||
raise KeyError("Invalid Key '%s'" % key) | ||
if key in self.rst_modules and key not in self.modules: | ||
from .sugarentry import SugarEntry | ||
self.modules[key] = SugarEntry(self, key) | ||
return self.modules[key] | ||
|
||
def _sendRequest(self, method, data): | ||
"""Sends an API request to the server, returns a dictionary with the | ||
server's response. | ||
It should not need to be called explicitly by the user, but rather by | ||
the other functions. | ||
Keyword arguments: | ||
method -- name of the method being called. | ||
data -- parameters to the function being called, should be in a list | ||
sorted by order of items | ||
""" | ||
data = json.dumps(data) | ||
args = {'method': method, 'input_type': 'json', | ||
'response_type': 'json', 'rest_data': data} | ||
params = urllib.parse.urlencode(args).encode('utf-8') | ||
response = urllib.request.urlopen(self._url, params) | ||
response = response.read().strip() | ||
if not response: | ||
raise SugarError({'name': 'Empty Result', | ||
'description': 'No data from SugarCRM.', | ||
'number': 0}) | ||
try: | ||
result = json.loads(response.decode('utf-8')) | ||
except: | ||
raise Exception(response.decode('utf-8')) | ||
if is_error(result): | ||
raise SugarError(result) | ||
return result | ||
|
||
def _login(self): | ||
""" | ||
Establish connection to the server. | ||
""" | ||
|
||
args = {'user_auth': {'user_name': self._username, | ||
'password': self.password}} | ||
|
||
x = self._sendRequest('login', args) | ||
try: | ||
self._session = x['id'] | ||
except KeyError: | ||
raise SugarUnhandledException | ||
|
||
def relate(self, main, *secondary, **kwargs): | ||
""" | ||
Relate two or more SugarEntry objects. | ||
Supported Keywords: | ||
relateby -> iterable of relationship names. Should match the | ||
length of *secondary. Defaults to secondary | ||
module table names (appropriate for most | ||
predefined relationships). | ||
""" | ||
|
||
relateby = kwargs.pop('relateby', [s._table for s in secondary]) | ||
args = [[main.module_name] * len(secondary), | ||
[main['id']] * len(secondary), | ||
relateby, | ||
[[s['id']] for s in secondary]] | ||
# Required for Sugar Bug 32064. | ||
if main.module_name == 'ProductBundles': | ||
args.append([[{'name': 'product_index', | ||
'value': '%d' % (i + 1)}] for i in range(len(secondary))]) | ||
return self.set_relationships(*args) | ||
|
||
@property | ||
def password(self): | ||
""" | ||
Returns an appropriately encoded password for this connection. | ||
- md5 hash for standard login. | ||
- plain text for ldap users | ||
""" | ||
if self._isldap: | ||
return self._password | ||
encode = hashlib.md5(self._password.encode('utf-8')) | ||
result = encode.hexdigest() | ||
return result | ||
|
||
|
||
def get_connection(url=API_URL, username=USERNAME, password=PASSWORD): | ||
if url and username and password: | ||
return Sugarcrm(url, username, password) | ||
raise SugarError({'name': 'Empty connection settings', | ||
'description': 'Empty connection settings', | ||
'number': 10}) |
Oops, something went wrong.