Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
an42rus committed Apr 12, 2020
1 parent a803489 commit b7e943b
Show file tree
Hide file tree
Showing 11 changed files with 1,100 additions and 0 deletions.
48 changes: 48 additions & 0 deletions examples.py
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)


16 changes: 16 additions & 0 deletions setup.py
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.'
)
5 changes: 5 additions & 0 deletions sugarcrm/__init__.py
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"
2 changes: 2 additions & 0 deletions sugarcrm/rest_framework/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .helpers import *
from .filterset import *
8 changes: 8 additions & 0 deletions sugarcrm/rest_framework/filterset.py
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
21 changes: 21 additions & 0 deletions sugarcrm/rest_framework/helpers.py
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)
8 changes: 8 additions & 0 deletions sugarcrm/settings.py
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', '')
178 changes: 178 additions & 0 deletions sugarcrm/sugarcrm.py
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})
Loading

0 comments on commit b7e943b

Please sign in to comment.