diff --git a/README.rst b/README.rst
index 6916b29..30fa8db 100644
--- a/README.rst
+++ b/README.rst
@@ -6,19 +6,18 @@ Let you access the OTRS API a pythonic-way.
Features
--------
-- Implements fully communication with the ``GenericTicketConnector``
+- Implements fully communication with the ``GenericTicketConnectorSOAP`` and ``GenericFAQConnectorSOAP``
provided as webservice example by OTRS;
-- dynamic fields and attachments are supported;
-- authentication is handled programmatically, per-request or per-session;
-- calls are wrapped in OTRSClient methods;
-- OTRS XML objects are mapped to Python-style objects see
- objects.Article and objects.Ticket.
+- Dynamic fields and attachments are supported;
+- Authentication is handled programmatically, per-request or per-session;
+- Calls are wrapped in OTRSClient methods;
+- OTRS XML objects are mapped to Python-style objects.
To be done
----------
- Test for python3 compatibility and make resulting changes;
-- improve and extend ``tests.py``;
+- Improve and extend ``tests.py``.
Compatibility
--------
@@ -31,33 +30,37 @@ Install
pip install python-otrs
-Using
------
+Ticket and Session Operations
+-----------------------------
-First make sure you installed the ``GenericTicketConnector`` webservice,
-see `official documentation`_.
+First make sure you installed the ``GenericTicketConnectorSOAP`` webservice,
+see `official documentation`_. The file GenericTicketConnectorSOAP.yml can be downloaded
+online as the basis for this service.
+
+Note: in older versions of OTRS, GenericTicketConnectorSOAP was called GenericTicketConnector
::
- from otrs.client import GenericTicketConnector
- from otrs.objects import Ticket, Article, DynamicField, Attachment
+ from otrs.ticket.template import GenericTicketConnectorSOAP
+ from otrs.client import GenericInterfaceClient
+ from otrs.ticket.objects import Ticket, Article, DynamicField, Attachment
server_uri = r'https://otrs.example.net'
- webservice_name = 'GenericTicketConnector'
- client = GenericTicketConnector(server_uri, webservice_name)
+ webservice_name = 'GenericTicketConnectorSOAP'
+ client = GenericInterfaceClient(server_uri, tc=GenericTicketConnectorSOAP(webservice_name))
Then authenticate, you have three choices :
::
# user session
- client.user_session_register('login', 'password')
+ client.tc.SessionCreate(user_login='login', password='password')
# customer_user session
- client.customer_user_session_register('login' , 'password')
+ client.tc.SessionCreate(customer_user_login='login' , password='password')
# save user in memory
- client.register_credentials(user='login', 'password')
+ client.register_credentials(user='login', password='password')
Play !
@@ -83,7 +86,7 @@ Create a ticket :
ContentType=mimetype, Filename="image001.png")
att_file.close()
- t_id, t_number = client.ticket_create(t, a, [df1, df2], [att1])
+ t_id, t_number = client.tc.TicketCreate(t, a, [df1, df2], [att1])
Update an article :
@@ -91,30 +94,30 @@ Update an article :
# changes the title of the ticket
t_upd = Ticket(Title='Updated ticket')
- client.ticket_update(t_id, t_upd)
+ client.tc.TicketUpdate(t_id, t_upd)
# appends a new article (attachments optional)
- new_article = Article(Subject='Moar info', Body='blabla', Charset='UTF8',
+ new_article = Article(Subject='More info', Body='blabla', Charset='UTF8',
MimeType='text/plain')
- client.update_ticket(article=new_article, attachments=None)
+ client.tc.TicketUpdate(article=new_article, attachments=None)
Search for tickets :
::
# returns all the tickets of customer 42
- tickets = client.ticket_search(CustomerID=42)
+ tickets = client.tc.TicketSearch(CustomerID=42)
# returns all tickets in queue Support
# for which Dynamic Field 'Project' starts with 'Pizza':
df2 = DynamicField(Name='Project', Value='Pizza%', Operator="Like")
- client.ticket_search(Queues='Support', dynamic_fields=[df_search])
+ client.tc.TicketSearch(Queues='Support', dynamic_fields=[df_search])
Retrieve a ticket :
::
- ticket = client.ticket_get(138, get_articles=True, get_dynamic_fields=True, get_attachments=True)
+ ticket = client.tc.TicketGet(138, get_articles=True, get_dynamic_fields=True, get_attachments=True)
article = ticket.articles()[0]
article.save_attachments(r'C:\temp')
@@ -122,3 +125,108 @@ Many options are possible with requests, you can use all the options
available in `official documentation`_.
.. _official documentation: http://otrs.github.io/doc/manual/admin/4.0/en/html/genericinterface.html#generic-ticket-connector
+
+Public FAQ Operations
+---------------------
+
+First, make sure you have installed the open-source FAQ add-on module into your OTRS system and added the
+GenericFAQConnectorSOAP web service by installing the GenericFAQConnector.yml file.
+
+::
+
+ from otrs.ticket.template import GenericTicketConnectorSOAP
+ from otrs.faq.template import GenericFAQConnectorSOAP
+ from otrs.client import GenericInterfaceClient
+
+ client = GenericInterfaceClient('https://otrs.mycompany.com', tc=GenericTicketConnectorSOAP('GenericTicketConnectorSOAP'), fc=GenericFAQConnectorSOAP('GenericFAQConnectorSOAP'))
+
+ # first, establish session with the TicketConnector
+ client.tc.SessionCreate(user_login='someotrsuser', password='p4ssw0rd')
+
+List FAQ Languages:
+
+::
+
+ langlist = client.fc.LanguageList()
+ for language in langlist:
+ print language.ID, language.Name
+
+List FAQ Categories that have Public FAQ items in them:
+
+::
+
+ catlist = client.fc.PublicCategoryList()
+ for category in catlist:
+ print category.ID, category.Name
+
+Retrieve a pubblic FAQ article by ID
+(note: FAQ Item ID is not the same as the item number!)
+
+::
+
+ # retrieves FAQ item ID #190 with attachment contents included
+ myfaqitem = client.fc.PublicFAQGet(190, get_attachments=True)
+ # print the FAQ's Problem field
+ print myfaqitem.Field2
+ # saves attachments to folder ./tempattach
+ myfaqitem.save_attachments('./tempattach')
+
+Search for an FAQ article
+
+::
+
+ #find all FAQ articles with Windows in title:
+ results = client.fc.PublicFAQSearch(Title='*Windows*')
+ for faqitemid in results:
+ print "Found FAQ item ID containing Windows: " + str(faqitemid)
+
+
+Custom Web Service Connectors
+-----------------------------
+
+For the FAQ operations above, note that we still needed the Ticket connector to provide access
+to the SessionCreate method. However, if your application only needs to work with FAQ articles
+and not tickets, you may wish to create a custom web service in OTRS that not only includes
+the four FAQ operations but also includes the SessionCreate operation to allow you to establish
+a session. This is very easy to accommodate in python-otrs.
+
+First, in OTRS, do the following:
+
+1. In OTRS Admin->Web Services, add a new web service without using a .yml file. Name it something
+ like 'ImprovedFAQConnectorSOAP'.
+2. In the settings for the web service, set the transport to HTTP::SOAP
+3. Click Save
+4. Click the 'Configure' button that has appeared next to HTTP::SOAP
+5. Set the namespace name to whatever you want (ex. http://www.otrs.org/FAQConnector).
+6. Enter the maximum message length you want (normally 10000000)
+7. Save the changes and go back to the main web service configuration screen.
+8. Add the operations you want to your custom webservice. For instance, for our improved FAQConnector,
+ you might add the four FAQ Operations and also the SessionCreate operation.
+9. Save your webservice
+
+Now that we have a web service in OTRS, we can use our custom web service in python-otrs. To do this,
+first create a 'template' for your new ImprovedFAQConnectorSOAP. Specify the namespace name assigned
+in step 5 above as the second parameter to the WebService() call.
+
+::
+
+ from otrs.faq.operations import LanguageList,PublicCategoryList,PublicFAQGet,PublicFAQSearch
+ from otrs.session.operations import SessionCreate
+ from otrs.client import WebService
+
+ def ImprovedFAQConnectorSOAP(webservice_name='ImprovedFAQConnectorSOAP'):
+ return WebService(webservice_name, 'http://www.otrs.org/FAQConnector', SessionCreate=SessionCreate(), LanguageList=LanguageList(),PublicCategoryList=PublicCategoryList(),PublicFAQGet=PublicFAQGet(),PublicFAQSearch=PublicFAQSearch())
+
+Now, use your improved FAQ connector:
+
+::
+
+ from otrs.client import GenericInterfaceClient
+
+ client = GenericInterfaceClient('https://otrs.mycompany.com', impfaqc=ImprovedFAQConnectorSOAP('ImprovedFAQConnectorSOAP'))
+
+ # first, establish session
+ client.impfaqc.SessionCreate(user_login='someotrsuser', password='p4ssw0rd')
+
+ # get an FAQ item:
+ client.impfaqc.PublicFAQGet(190)
\ No newline at end of file
diff --git a/otrs/client.py b/otrs/client.py
index 339589c..f33fd53 100644
--- a/otrs/client.py
+++ b/otrs/client.py
@@ -1,46 +1,64 @@
+"""OTRS :: client."""
try:
import urllib.request as urllib2
except ImportError:
import urllib2
from posixpath import join as urljoin
import xml.etree.ElementTree as etree
-from .objects import Ticket, OTRSObject, DynamicField, extract_tagname
+from otrs.objects import OTRSObject, extract_tagname
import codecs
import sys
+import abc
class OTRSError(Exception):
+ """Base class for OTRS Errors."""
+
def __init__(self, fd):
+ """Initialize OTRS Error."""
self.code = fd.getcode()
self.msg = fd.read()
def __str__(self):
+ """Return error message for OTRS Error."""
return '{} : {}'.format(self.code, self.msg)
class SOAPError(OTRSError):
+ """OTRS Error originating from an incorrect SOAP request."""
+
def __init__(self, tag):
+ """Initialize OTRS SOAPError."""
d = {extract_tagname(i): i.text for i in tag.getchildren()}
self.errcode = d['ErrorCode']
self.errmsg = d['ErrorMessage']
def __str__(self):
+ """Return error message for OTRS SOAPError."""
return '{} ({})'.format(self.errmsg, self.errcode)
class NoCredentialsException(OTRSError):
+ """OTRS Error that is returned when no credentials are provided."""
+
def __init__(self):
+ """Initialize OTRS NoCredentialsException."""
pass
def __str__(self):
+ """Return error message for OTRS NoCredentialsException."""
return 'Register credentials first with register_credentials() method'
class WrongOperatorException(OTRSError):
+ """OTRS Error that is returned when a non-existent operation is called."""
+
def __init__(self):
+ """Initialize OTRS WrongOperatorException."""
pass
def __str__(self):
+ """Return error message for OTRS WrongOperatorException."""
return '''Please use one of the following operators for the
query on a dynamic field: `Equals`, `Like`, `GreaterThan`,
`GreaterThanEquals`, `SmallerThan` or `SmallerThanEquals`.
@@ -48,9 +66,7 @@ def __str__(self):
def authenticated(func):
- """ Decorator to add authentication parameters to a request
- """
-
+ """Decorator to add authentication parameters to a request."""
def add_auth(self, *args, **kwargs):
if self.session_id:
kwargs['SessionID'] = self.session_id
@@ -65,46 +81,79 @@ def add_auth(self, *args, **kwargs):
return add_auth
-SOAP_ENVELOPPE = """
-
-
- {}
-
-"""
+class OperationBase(object):
+ """Base class for OTRS operations."""
+ __metaclass__ = abc.ABCMeta
-class GenericTicketConnector(object):
- """ Client for the GenericTicketConnector SOAP API
-
- see http://otrs.github.io/doc/manual/admin/3.3/en/html/genericinterface.html
- """
-
- def __init__(self, server, webservice_name='GenericTicketConnector', ssl_context=None):
- """ @param server : the http(s) URL of the root installation of OTRS
- (e.g: https://tickets.example.net)
-
- @param webservice_name : the name of the installed webservice
- (choosen by the otrs admin).
- """
-
- self.endpoint = urljoin(
- server, 'otrs/nph-genericinterface.pl/Webservice/',
- webservice_name)
- self.login = None
- self.password = None
- self.session_id = None
- self.ssl_context = ssl_context
-
- def register_credentials(self, login, password):
- """ Save the identifiers in memory, they will be used with each
- subsequent request requiring authentication
- """
- self.login = login
- self.password = password
+ def __init__(self, opName=None):
+ """Initialize OperationBase."""
+ if opName is None:
+ self.operName = type(self).__name__
+ else:
+ self.operName = opName # otrs connector operation name
+ self.wsObject = None # web services object this operation belongs to
+
+ def getWebServiceObjectAttribute(self, attribName):
+ """Return attribute of the WebService object."""
+ return getattr(self.wsObject, attribName)
+
+ def getClientObjectAttribute(self, attribName):
+ """Return attribute of the clientobject of the WebService object."""
+ return self.wsObject.getClientObjectAttribute(attribName)
+
+ def setClientObjectAttribute(self, attribName, attribValue):
+ """Set attribute of the clientobject of the WebService object."""
+ self.wsObject.setClientObjectAttribute(attribName, attribValue)
+
+ @abc.abstractmethod
+ def __call__(self):
+ """."""
+ return
+
+ @property
+ def endpoint(self):
+ """Return endpoint of WebService object."""
+ return self.getWebServiceObjectAttribute('endpoint')
+
+ @property
+ def login(self):
+ """Get login attribute of the clientobject of the WebService object."""
+ return self.getClientObjectAttribute('login')
+
+ @property
+ def password(self):
+ """Return password attribute of the clientobject of the WebService."""
+ return self.getClientObjectAttribute('password')
+
+ @property
+ def ssl_context(self):
+ """Return ssl_context of the clientobject of the WebService."""
+ return self.getClientObjectAttribute('ssl_context')
+
+ @property
+ def session_id(self):
+ """Return session_id of the clientobject of the WebService object."""
+ return self.getClientObjectAttribute('session_id')
+
+ @session_id.setter
+ def session_id(self, sessionid):
+ """Set session_id of the clientobject of the WebService object."""
+ self.setClientObjectAttribute('session_id', sessionid)
+
+ @property
+ def soap_envelope(self):
+ """Return soap envelope for WebService object."""
+ soap_envelope = '{}' + \
+ ''
+ return soap_envelope
def req(self, reqname, *args, **kwargs):
- """ Wrapper arround a SOAP request
+ """Wrapper arround a SOAP request.
+
@param reqname: the SOAP name of the request
@param kwargs : to define the tags included in the request.
@return : the full etree.Element of the response
@@ -115,7 +164,6 @@ def req(self, reqname, *args, **kwargs):
- list of `OTRSObject`s: each `OTRSObject`s in the list
will be serialized with their `.to_xml()` (used for
dynamic fields and attachments).
-
"""
xml_req_root = etree.Element(reqname)
@@ -135,7 +183,8 @@ def req(self, reqname, *args, **kwargs):
self.endpoint, self._pack_req(xml_req_root),
{'Content-Type': 'text/xml;charset=utf-8'})
- if (sys.version_info[0] == 3 and sys.version_info < (3,4,3)) or sys.version_info < (2,7,9):
+ if ((sys.version_info[0] == 3 and sys.version_info < (3, 4, 3)) or
+ (sys.version_info < (2, 7, 9))):
fd = urllib2.urlopen(request)
else:
fd = urllib2.urlopen(request, context=self.ssl_context)
@@ -160,7 +209,8 @@ def req(self, reqname, *args, **kwargs):
@staticmethod
def _unpack_resp_several(element):
- """
+ """Unpack an etree element and return a list of children.
+
@param element : a etree.Element
@return : a list of etree.Element
"""
@@ -168,191 +218,175 @@ def _unpack_resp_several(element):
@staticmethod
def _unpack_resp_one(element):
- """
+ """Unpack an etree element an return first child.
+
@param element : a etree.Element
@return : a etree.Element (first child of the response)
"""
return element.getchildren()[0].getchildren()[0].getchildren()[0]
- @staticmethod
- def _pack_req(element):
- """
+ def _pack_req(self, element):
+ """Pack an etree Element.
+
@param element : a etree.Element
@returns : a string, wrapping element within the request tags
+ """
+ return self.soap_envelope.format(
+ codecs.decode(etree.tostring(element), 'utf-8')).encode('utf-8')
+
+
+class WebService(object):
+ """Base class for OTRS Web Service."""
+
+ def __init__(self, wsName, wsNamespace, **kwargs):
+ """Initialize WebService object."""
+ self.clientObject = None # link to parent client object
+ self.wsName = wsName # name for OTRS web service
+ self.wsNamespace = wsNamespace # OTRS namespace url
+
+ # add all variables in kwargs into the local dictionary
+ self.__dict__.update(kwargs)
+
+ # for operations, set backlinks to their associated webservice
+ for arg in kwargs:
+ # if attribute is type OperationBase, set backlink to WebService
+ if isinstance(getattr(self, arg), OperationBase):
+ getattr(self, arg).wsObject = self
+
+ # set defaults if attributes are not present
+ if not hasattr(self, 'wsRequestNameScheme'):
+ self.wsRequestNameScheme = 'DATA'
+ if not hasattr(self, 'wsResponseNameScheme'):
+ ns = 'DATA'
+ self.wsResponseNameScheme = ns
+
+ def getClientObjectAttribute(self, attribName):
+ """Return attribute of the clientobject of the WebService object."""
+ return getattr(self.clientObject, attribName)
+
+ def setClientObjectAttribute(self, attribName, attribValue):
+ """Set attribute of the clientobject of the WebService object."""
+ setattr(self.clientObject, attribName, attribValue)
+
+ @property
+ def endpoint(self):
+ """Return endpoint of WebService object."""
+ return urljoin(self.getClientObjectAttribute('giurl'), self.wsName)
+
+class GenericInterfaceClient(object):
+ """Client for the OTRS Generic Interface."""
+
+ def __init__(self, server, ssl_context=None, **kwargs):
+ """Initialize GenericInterfaceClient.
+
+ @param server : the http(s) URL of the root installation of OTRS
+ (e.g: https://tickets.example.net)
"""
- return SOAP_ENVELOPPE.format(codecs.decode(etree.tostring(element),'utf-8')).encode('utf-8')
+ # add all variables in kwargs into the local dictionary
+ self.__dict__.update(kwargs)
+
+ # for webservices attached to this client object, backlink them
+ # to this client object to allow access to session login/password
+
+ for arg in kwargs:
+ # set backlink for web services to this obj
+ if isinstance(getattr(self, arg), WebService):
+ getattr(self, arg).clientObject = self
+ self.login = None
+ self.password = None
+ self.session_id = None
+ self.ssl_context = ssl_context
+ self.giurl = urljoin(
+ server, 'otrs/nph-genericinterface.pl/Webservice/')
+
+ def register_credentials(self, login, password):
+ """Save the identifiers in memory.
+
+ They will be used with each subsequent request requiring authentication
+ """
+ self.login = login
+ self.password = password
+
+
+class OldGTCClass(GenericInterfaceClient):
+ """DEPRECATED - Old generic ticket connector class.
+
+ Used for backward compatibility with previous versions. All
+ methods in here are deprecated.
+ """
def session_create(self, password, user_login=None,
- customer_user_login=None):
- """ Logs the user or customeruser in
+ customer_user_login=None):
+ """DEPRECATED - creates a session for an User or CustomerUser.
@returns the session_id
"""
- if user_login:
- ret = self.req('SessionCreate',
- UserLogin=user_login,
- Password=password)
- else:
- ret = self.req('SessionCreate',
- CustomerUserLogin=customer_user_login,
- Password=password)
- signal = self._unpack_resp_one(ret)
- session_id = signal.text
- return session_id
+ self.tc.SessionCreate(password, user_login=user_login,
+ customer_user_login=customer_user_login)
def user_session_register(self, user, password):
- """ Logs the user in and stores the session_id for subsequent requests
- """
- self.session_id = self.session_create(
- password=password,
- user_login=user)
+ """DEPRECATED - creates a session for an User."""
+ self.session_create(password=password, user_login=user)
def customer_user_session_register(self, user, password):
- """ Logs the customer_user in and stores the session_id for subsequent
- requests.
- """
- self.session_id = self.session_create(
- password=password,
- customer_user_login=user)
+ """DEPRECATED - creates a session for a CustomerUser."""
+ self.session_create(password=password, customer_user_login=user)
@authenticated
- def ticket_get(self, ticket_id, get_articles=False,
- get_dynamic_fields=False,
- get_attachments=False, *args, **kwargs):
- """ Get a ticket by id ; beware, TicketID != TicketNumber
-
- @param ticket_id : the TicketID of the ticket
- @param get_articles : grab articles linked to the ticket
- @param get_dynamic_fields : include dynamic fields in result
- @param get_attachments : include attachments in result
-
- @return a `Ticket`, Ticket.articles() will give articles if relevant.
- Ticket.articles()[i].attachments() will return the attachments for
- an article, wheres Ticket.articles()[i].save_attachments()
- will save the attachments of article[i] to the specified folder.
- """
- params = {'TicketID': str(ticket_id)}
- params.update(kwargs)
- if get_articles:
- params['AllArticles'] = True
- if get_dynamic_fields:
- params['DynamicFields'] = True
- if get_attachments:
- params['Attachments'] = True
-
- ret = self.req('TicketGet', **params)
- return Ticket.from_xml(self._unpack_resp_one(ret))
+ def ticket_create(self, ticket, article, dynamic_fields=None,
+ attachments=None, **kwargs):
+ """DEPRECATED - now calls operation of GenericTicketConnectorSOAP."""
+ return self.tc.TicketCreate(ticket,
+ article,
+ dynamic_fields=dynamic_fields,
+ attachments=attachments,
+ **kwargs)
@authenticated
- def ticket_search(self, dynamic_fields=None, **kwargs):
- """
- @param dynamic_fields a list of Dynamic Fields, in addition to
- the combination of `Name` and `Value`, also an `Operator` for the
- comparison is expexted `Equals`, `Like`, `GreaterThan`,
- `GreaterThanEquals`, `SmallerThan` or `SmallerThanEquals`.
- The `Like` operator accepts a %-sign as wildcard.
- @returns a list of matching TicketID
- """
- df_search_list = []
- dynamic_field_requirements = ('Name', 'Value', 'Operator')
- if not (dynamic_fields is None):
- for df in dynamic_fields:
- df.check_fields(dynamic_field_requirements)
- if df.Operator == 'Equals':
- df_search = DynamicField(Equals=df.Value)
- elif df.Operator == 'Like':
- df_search = DynamicField(Like=df.Value)
- elif df.Operator == 'GreaterThan':
- df_search = DynamicField(GreaterThan=df.Value)
- elif df.Operator == 'GreaterThanEquals':
- df_search = DynamicField(GreaterThanEquals=df.Value)
- elif df.Operator == 'SmallerThan':
- df_search = DynamicField(SmallerThan=df.Value)
- elif df.Operator == 'SmallerThan':
- df_search = DynamicField(SmallerThan=df.Value)
- else:
- raise WrongOperatorException()
- df_search.XML_NAME = 'DynamicField_{0}'.format(df.Name)
- df_search_list.append(df_search)
- kwargs['DynamicFields'] = df_search_list
-
- ret = self.req('TicketSearch', **kwargs)
- return [int(i.text) for i in self._unpack_resp_several(ret)]
+ def ticket_get(self, ticket_id, get_articles=False,
+ get_dynamic_fields=False, get_attachments=False,
+ *args, **kwargs):
+ """DEPRECATED - now calls operation of GenericTicketConnectorSOAP."""
+ return self.tc.TicketGet(ticket_id,
+ get_articles=get_articles,
+ get_dynamic_fields=get_dynamic_fields,
+ get_attachments=get_attachments,
+ *args,
+ **kwargs)
@authenticated
- def ticket_create(self, ticket, article, dynamic_fields=None,
- attachments=None, **kwargs):
- """
- @param ticket a Ticket
- @param article an Article
- @param dynamic_fields a list of Dynamic Fields
- @param attachments a list of Attachments
- @returns the ticketID, TicketNumber
- """
- ticket_requirements = (
- ('StateID', 'State'), ('PriorityID', 'Priority'),
- ('QueueID', 'Queue'), )
- article_requirements = ('Subject', 'Body', 'Charset', 'MimeType')
- dynamic_field_requirements = ('Name', 'Value')
- attachment_field_requirements = ('Content', 'ContentType', 'Filename')
- ticket.check_fields(ticket_requirements)
- article.check_fields(article_requirements)
- if not (dynamic_fields is None):
- for df in dynamic_fields:
- df.check_fields(dynamic_field_requirements)
- if not (attachments is None):
- for att in attachments:
- att.check_fields(attachment_field_requirements)
- ret = self.req('TicketCreate', ticket=ticket, article=article,
- dynamic_fields=dynamic_fields,
- attachments=attachments, **kwargs)
- elements = self._unpack_resp_several(ret)
- infos = {extract_tagname(i): int(i.text) for i in elements}
- return infos['TicketID'], infos['TicketNumber']
+ def ticket_search(self, dynamic_fields=None, **kwargs):
+ """DEPRECATED - now calls operation of GenericTicketConnectorSOAP."""
+ return self.tc.TicketSearch(dynamic_fields=dynamic_fields, **kwargs)
@authenticated
def ticket_update(self, ticket_id=None, ticket_number=None,
ticket=None, article=None, dynamic_fields=None,
attachments=None, **kwargs):
- """
- @param ticket_id the ticket ID of the ticket to modify
- @param ticket_number the ticket Number of the ticket to modify
- @param ticket a ticket containing the fields to change on ticket
- @param article a new Article to append to the ticket
- @param dynamic_fields a list of Dynamic Fields to change on ticket
- @param attachments a list of Attachments for a newly appended article
- @returns the ticketID, TicketNumber
-
-
- Mandatory : - `ticket_id` xor `ticket_number`
- - `ticket` or `article` or `dynamic_fields`
-
- """
- if not (ticket_id is None):
- kwargs['TicketID'] = ticket_id
- elif not (ticket_number is None):
- kwargs['TicketNumber'] = ticket_number
- else:
- raise ValueError('requires either ticket_id or ticket_number')
-
- if (ticket is None) and (article is None) and (dynamic_fields is None):
- raise ValueError(
- 'requires at least one among ticket, article, dynamic_fields')
- elif (article is None) and not (attachments is None):
- raise ValueError(
- 'Attachments can only be created for a newly appended article')
- else:
- if (ticket):
- kwargs['Ticket'] = ticket
- if (article):
- kwargs['Article'] = article
- if (dynamic_fields):
- kwargs['DynamicField'] = dynamic_fields
- if (attachments):
- kwargs['Attachment'] = attachments
-
- ret = self.req('TicketUpdate', **kwargs)
- elements = self._unpack_resp_several(ret)
- infos = {extract_tagname(i): int(i.text) for i in elements}
- return infos['TicketID'], infos['TicketNumber']
+ """DEPRECATED - now calls operation of GenericTicketConnectorSOAP."""
+ return self.tc.TicketUpdate(ticket_id=ticket_id,
+ ticket_number=ticket_number,
+ ticket=ticket,
+ article=article,
+ dynamic_fields=dynamic_fields,
+ attachments=attachments, **kwargs)
+
+
+def GenericTicketConnector(server,
+ webservice_name='GenericTicketConnector',
+ ssl_context=None):
+ """DEPRECATED - now calls operation of GenericTicketConnectorSOAP."""
+ from otrs.ticket.operations import TicketCreate, TicketGet
+ from otrs.ticket.operations import TicketSearch, TicketUpdate
+ from otrs.session.operations import SessionCreate
+ ticketconnector = WebService(
+ webservice_name,
+ 'http://www.otrs.org/TicketConnector',
+ ssl_context=ssl_context,
+ SessionCreate=SessionCreate(),
+ TicketCreate=TicketCreate(),
+ TicketGet=TicketGet(),
+ TicketSearch=TicketSearch(),
+ TicketUpdate=TicketUpdate())
+ return OldGTCClass(server, tc=ticketconnector)
diff --git a/otrs/faq/__init__.py b/otrs/faq/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/otrs/faq/objects.py b/otrs/faq/objects.py
new file mode 100644
index 0000000..df47555
--- /dev/null
+++ b/otrs/faq/objects.py
@@ -0,0 +1,21 @@
+"""OTRS :: faq :: objects."""
+from otrs.objects import OTRSObject, Attachment, AttachmentContainer
+
+
+class Category(OTRSObject):
+ """An OTRS FAQ Category."""
+
+ XML_NAME = 'Category'
+
+
+class Language(OTRSObject):
+ """An OTRS FAQ Language."""
+
+ XML_NAME = 'Language'
+
+
+class FAQItem(OTRSObject, AttachmentContainer):
+ """An OTRS FAQ Item."""
+
+ XML_NAME = 'FAQItem'
+ CHILD_MAP = {'Attachment': Attachment}
diff --git a/otrs/faq/operations.py b/otrs/faq/operations.py
new file mode 100644
index 0000000..c67ba05
--- /dev/null
+++ b/otrs/faq/operations.py
@@ -0,0 +1,73 @@
+"""OTRS :: faq :: operations."""
+from otrs.faq.objects import Category as CategoryObject
+from otrs.faq.objects import Language as LanguageObject
+from otrs.faq.objects import FAQItem as FAQItemObject
+from otrs.client import OperationBase, authenticated
+
+
+class FAQ(OperationBase):
+ """Base class for OTRS FAQ:: operations."""
+
+
+class LanguageList(FAQ):
+ """Class to handle OTRS ITSM FAQ::LanguageList operation."""
+
+ @authenticated
+ def __call__(self, *args, **kwargs):
+ """Return the Language List from FAQ.
+
+ @returns list of languages
+ """
+ ret = self.req('LanguageList', **kwargs)
+ elements = self._unpack_resp_several(ret)
+ return [LanguageObject.from_xml(language) for language in elements]
+
+
+class PublicCategoryList(FAQ):
+ """Class to handle OTRS ITSM FAQ::PublicCategoryList operation."""
+
+ @authenticated
+ def __call__(self, **kwargs):
+ """Return the Public Category List from FAQ.
+
+ @returns list of category objects
+ """
+ ret = self.req('PublicCategoryList', **kwargs)
+ elements = self._unpack_resp_several(ret)
+ return [CategoryObject.from_xml(category) for category in elements]
+
+
+class PublicFAQGet(FAQ):
+ """Class to handle OTRS ITSM FAQ::PublicFAQGet operation."""
+
+ @authenticated
+ def __call__(self, item_id, get_attachments=False, **kwargs):
+ """Get a public FAQItem by id.
+
+ @param item_id : the ItemID of the public FAQItem
+ NOTE: ItemID != FAQ Number
+
+ @return an `FAQItem`
+ """
+ params = {'ItemID': str(item_id)}
+ params.update(kwargs)
+ if get_attachments:
+ params['GetAttachmentContents'] = 1
+ else:
+ params['GetAttachmentContents'] = 0
+
+ ret = self.req('PublicFAQGet', **params)
+ return FAQItemObject.from_xml(self._unpack_resp_one(ret))
+
+
+class PublicFAQSearch(FAQ):
+ """Class to handle OTRS ITSM FAQ :: PublicFAQSearch operation."""
+
+ @authenticated
+ def __call__(self, *args, **kwargs):
+ """Search for matching public FAQItems.
+
+ @returns a list of matching public FAQItem IDs
+ """
+ ret = self.req('PublicFAQSearch', **kwargs)
+ return [int(i.text) for i in self._unpack_resp_several(ret)]
diff --git a/otrs/faq/template.py b/otrs/faq/template.py
new file mode 100644
index 0000000..d7cf5ed
--- /dev/null
+++ b/otrs/faq/template.py
@@ -0,0 +1,15 @@
+"""OTRS :: faq :: template."""
+from otrs.faq.operations import LanguageList, PublicCategoryList
+from otrs.faq.operations import PublicFAQGet, PublicFAQSearch
+from otrs.client import WebService
+
+def GenericFAQConnectorSOAP(webservice_name='GenericFAQConnectorSOAP'):
+ """Return a GenericFAQConnectorSOAP Webservice object.
+
+ @returns a WebService object with the GenericFAQConnectorSOAP operations
+ """
+ return WebService(webservice_name, 'http://www.otrs.org/FAQConnector',
+ LanguageList=LanguageList(),
+ PublicCategoryList=PublicCategoryList(),
+ PublicFAQGet=PublicFAQGet(),
+ PublicFAQSearch=PublicFAQSearch())
diff --git a/otrs/objects.py b/otrs/objects.py
index fc99d84..65a8c5e 100644
--- a/otrs/objects.py
+++ b/otrs/objects.py
@@ -1,24 +1,26 @@
+"""OTRS :: objects."""
from __future__ import unicode_literals
import xml.etree.ElementTree as etree
import os
+import sys
import base64
-
class OTRSObject(object):
- """ Represents an object for OTRS (mappable to an XML element)
- """
+ """Represents an object for OTRS (mappable to an XML element)."""
# Map : {'TagName' -> Class}
-
CHILD_MAP = {}
def __init__(self, *args, **kwargs):
+ """Initialize OTRS Object."""
self.attrs = kwargs
self.childs = {}
def __getattr__(self, k):
- """ attrs are simple xml child tags (val), complex children,
+ """Get an attribute for aan OTRSObject.
+
+ attrs are simple xml child tags (val), complex children,
are accessible via dedicated methods.
@returns a simple type
@@ -27,7 +29,8 @@ def __getattr__(self, k):
@classmethod
def from_xml(cls, xml_element):
- """
+ """Create an OTRS Object from xml.
+
@param xml_element an etree.Element
@returns an OTRSObject
"""
@@ -57,7 +60,8 @@ def from_xml(cls, xml_element):
return obj
def add_child(self, childobj):
- """
+ """Add a child object to an OTRS Object.
+
@param childobj : an OTRSObject
"""
xml_name = childobj.XML_NAME
@@ -68,7 +72,8 @@ def add_child(self, childobj):
self.childs[xml_name] = [childobj]
def check_fields(self, fields):
- """ Checks that the list of fields is bound
+ """Check that the list of fields is bound.
+
@param fields rules, as list
items n fields can be either :
@@ -92,21 +97,25 @@ def check_fields(self, fields):
raise ValueError('{} should be filled'.format(i))
def to_xml(self):
- """
+ """Create an XML representation of an OTRS Object.
+
@returns am etree.Element
"""
root = etree.Element(self.XML_NAME)
for k, v in self.attrs.items():
e = etree.Element(k)
- if isinstance(e, str): # True
+ if isinstance(e, str):
v = v.encode('utf-8')
- e.text = unicode(v)
+ if sys.version_info[0] == 3:
+ e.text = str(v)
+ else:
+ e.text = unicode(v)
root.append(e)
return root
def extract_tagname(element):
- """ Returns the name of the tag, without namespace
+ """Return the name of the tag, without namespace.
element.tag lib gives "{namespace}tagname", we want only "tagname"
@@ -119,16 +128,15 @@ def extract_tagname(element):
except IndexError:
# if it's not namespaced, then return the tag name itself
return element.tag
- #raise ValueError('"{}" is not a tag name'.format(qualified_name))
+ # raise ValueError('"{}" is not a tag name'.format(qualified_name))
def autocast(s):
- """ Tries to guess the simple type and convert the value to it.
+ """Try to guess the simple type and convert the value to it.
@param s string
@returns the relevant type : a float, string or int
"""
-
try:
return int(s)
except ValueError:
@@ -139,31 +147,35 @@ def autocast(s):
class Attachment(OTRSObject):
+ """An OTRS attachment."""
+
XML_NAME = 'Attachment'
class DynamicField(OTRSObject):
+ """An OTRS dynamic field."""
+
XML_NAME = 'DynamicField'
-class Article(OTRSObject):
- XML_NAME = 'Article'
- CHILD_MAP = {'Attachment': Attachment, 'DynamicField': DynamicField}
+class AttachmentContainer(object):
+ """For objects that can have attachments in them (ex. tickets, articles).
+
+ They should inherit this class in addition to OTRSObject.
+ """
def attachments(self):
+ """Return the dynamic fields for an object ket as a list.
+
+ @returns a list of Attachment objects.
+ """
try:
return self.childs['Attachment']
except KeyError:
return []
- def dynamicfields(self):
- try:
- return self.childs['DynamicField']
- except KeyError:
- return []
-
def save_attachments(self, folder):
- """ Saves the attachments of an article to the specified folder
+ """Save the attachments of an article to the specified folder.
@param folder : a str, folder to save the attachments
"""
@@ -177,19 +189,35 @@ def save_attachments(self, folder):
ffile.close()
-class Ticket(OTRSObject):
- XML_NAME = 'Ticket'
- CHILD_MAP = {'Article': Article, 'DynamicField': DynamicField}
+class DynamicFieldContainer(object):
+ """For objects that can have dynamic fields in them (ex. tickets, articles).
+
+ They should inherit this class in addition to OTRSObject.
+ """
- def articles(self):
- try:
- return self.childs['Article']
- except KeyError:
- return []
-
def dynamicfields(self):
+ """Return the dynamic fields for an object ket as a list.
+
+ @returns a list of DynamicField objects.
+ """
try:
return self.childs['DynamicField']
except KeyError:
return []
+
+# the two functions below are here only for backward compatibility
+# with old code that imported these classes from this file
+# the classes are now in tickets/objects.py
+
+
+def Ticket(*args, **kwargs):
+ """Return an OTRS ticket."""
+ import otrs.ticket.objects
+ return otrs.ticket.objects.Ticket(*args, **kwargs)
+
+
+def Article(*args, **kwargs):
+ """Return an OTRS article."""
+ import otrs.ticket.objects
+ return otrs.ticket.objects.Article(*args, **kwargs)
diff --git a/otrs/session/__init__.py b/otrs/session/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/otrs/session/operations.py b/otrs/session/operations.py
new file mode 100644
index 0000000..a63219d
--- /dev/null
+++ b/otrs/session/operations.py
@@ -0,0 +1,33 @@
+"""OTRS :: session :: operations."""
+from otrs.client import OperationBase
+
+
+class Session(OperationBase):
+ """Base class for OTRS Session:: operations."""
+
+
+class SessionCreate(Session):
+ """Class to handle OTRS Session::SessionCreate operation."""
+
+ def __call__(self, password, user_login=None, customer_user_login=None):
+ """Create an User session or CustomerUser session.
+
+ @returns the session_id
+ """
+ if user_login:
+ ret = self.req('SessionCreate',
+ UserLogin=user_login,
+ Password=password)
+ else:
+ ret = self.req('SessionCreate',
+ CustomerUserLogin=customer_user_login,
+ Password=password)
+ signal = self._unpack_resp_one(ret)
+ session_id = signal.text
+
+ # sets the session id for the entire client to this
+ self.session_id = session_id
+
+ # returns the session id in case you want it,
+ # but its not normally needed
+ return session_id
diff --git a/otrs/ticket/__init__.py b/otrs/ticket/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/otrs/ticket/objects.py b/otrs/ticket/objects.py
new file mode 100644
index 0000000..2481658
--- /dev/null
+++ b/otrs/ticket/objects.py
@@ -0,0 +1,27 @@
+"""OTRS :: ticket :: objects."""
+from otrs.objects import OTRSObject, Attachment, DynamicField
+from otrs.objects import AttachmentContainer, DynamicFieldContainer
+
+
+class Article(OTRSObject, AttachmentContainer, DynamicFieldContainer):
+ """An OTRS article."""
+
+ XML_NAME = 'Article'
+ CHILD_MAP = {'Attachment': Attachment, 'DynamicField': DynamicField}
+
+
+class Ticket(OTRSObject, DynamicFieldContainer):
+ """An OTRS ticket."""
+
+ XML_NAME = 'Ticket'
+ CHILD_MAP = {'Article': Article, 'DynamicField': DynamicField}
+
+ def articles(self):
+ """Return the articles for a ticket as a list.
+
+ @returns a list of Article objects.
+ """
+ try:
+ return self.childs['Article']
+ except KeyError:
+ return []
diff --git a/otrs/ticket/operations.py b/otrs/ticket/operations.py
new file mode 100644
index 0000000..a25317b
--- /dev/null
+++ b/otrs/ticket/operations.py
@@ -0,0 +1,168 @@
+"""OTRS :: ticket :: operations."""
+from otrs.ticket.objects import Ticket as TicketObject
+from otrs.client import OperationBase, authenticated, WrongOperatorException
+from otrs.objects import extract_tagname, DynamicField
+
+
+class Ticket(OperationBase):
+ """Base class for OTRS Ticket:: operations."""
+
+
+class TicketCreate(Ticket):
+ """Class to handle OTRS Ticket::TicketCreate operation."""
+
+ @authenticated
+ def __call__(self, ticket, article, dynamic_fields=None,
+ attachments=None, **kwargs):
+ """Create a new ticket.
+
+ @param ticket a Ticket
+ @param article an Article
+ @param dynamic_fields a list of Dynamic Fields
+ @param attachments a list of Attachments
+ @returns the ticketID, TicketNumber
+ """
+ ticket_requirements = (
+ ('StateID', 'State'), ('PriorityID', 'Priority'),
+ ('QueueID', 'Queue'), )
+ article_requirements = ('Subject', 'Body', 'Charset', 'MimeType')
+ dynamic_field_requirements = ('Name', 'Value')
+ attachment_field_requirements = ('Content', 'ContentType', 'Filename')
+ ticket.check_fields(ticket_requirements)
+ article.check_fields(article_requirements)
+ if not (dynamic_fields is None):
+ for df in dynamic_fields:
+ df.check_fields(dynamic_field_requirements)
+ if not (attachments is None):
+ for att in attachments:
+ att.check_fields(attachment_field_requirements)
+ ret = self.req('TicketCreate', ticket=ticket, article=article,
+ dynamic_fields=dynamic_fields,
+ attachments=attachments, **kwargs)
+ elements = self._unpack_resp_several(ret)
+ infos = {extract_tagname(i): int(i.text) for i in elements}
+ return infos['TicketID'], infos['TicketNumber']
+
+
+class TicketGet(Ticket):
+ """Class to handle OTRS Ticket::TicketGet operation."""
+
+ @authenticated
+ def __call__(self, ticket_id, get_articles=False,
+ get_dynamic_fields=False,
+ get_attachments=False, *args, **kwargs):
+ """Get a ticket by id ; beware, TicketID != TicketNumber.
+
+ @param ticket_id : the TicketID of the ticket
+ @param get_articles : grab articles linked to the ticket
+ @param get_dynamic_fields : include dynamic fields in result
+ @param get_attachments : include attachments in result
+
+ @return a `Ticket`, Ticket.articles() will give articles if relevant.
+ Ticket.articles()[i].attachments() will return the attachments for
+ an article, wheres Ticket.articles()[i].save_attachments()
+ will save the attachments of article[i] to the specified folder.
+ """
+ params = {'TicketID': str(ticket_id)}
+ params.update(kwargs)
+ if get_articles:
+ params['AllArticles'] = True
+ if get_dynamic_fields:
+ params['DynamicFields'] = True
+ if get_attachments:
+ params['Attachments'] = True
+
+ ret = self.req('TicketGet', **params)
+ return TicketObject.from_xml(self._unpack_resp_one(ret))
+
+
+class TicketSearch(Ticket):
+ """Class to handle OTRS Ticket::TicketSearch operation."""
+
+ @authenticated
+ def __call__(self, dynamic_fields=None, **kwargs):
+ """Search for a ticket by.
+
+ @param dynamic_fields a list of Dynamic Fields, in addition to
+ the combination of `Name` and `Value`, also an `Operator` for the
+ comparison is expexted `Equals`, `Like`, `GreaterThan`,
+ `GreaterThanEquals`, `SmallerThan` or `SmallerThanEquals`.
+ The `Like` operator accepts a %-sign as wildcard.
+ @returns a list of matching TicketID
+ """
+ df_search_list = []
+ dynamic_field_requirements = ('Name', 'Value', 'Operator')
+ if not (dynamic_fields is None):
+ for df in dynamic_fields:
+ df.check_fields(dynamic_field_requirements)
+ if df.Operator == 'Equals':
+ df_search = DynamicField(Equals=df.Value)
+ elif df.Operator == 'Like':
+ df_search = DynamicField(Like=df.Value)
+ elif df.Operator == 'GreaterThan':
+ df_search = DynamicField(GreaterThan=df.Value)
+ elif df.Operator == 'GreaterThanEquals':
+ df_search = DynamicField(GreaterThanEquals=df.Value)
+ elif df.Operator == 'SmallerThan':
+ df_search = DynamicField(SmallerThan=df.Value)
+ elif df.Operator == 'SmallerThan':
+ df_search = DynamicField(SmallerThan=df.Value)
+ else:
+ raise WrongOperatorException()
+ df_search.XML_NAME = 'DynamicField_{0}'.format(df.Name)
+ df_search_list.append(df_search)
+ kwargs['DynamicFields'] = df_search_list
+
+ ret = self.req('TicketSearch', **kwargs)
+ return [int(i.text) for i in self._unpack_resp_several(ret)]
+
+
+class TicketUpdate(Ticket):
+ """Class to handle OTRS Ticket::TicketUpdate operation."""
+
+ @authenticated
+ def __call__(self, ticket_id=None, ticket_number=None,
+ ticket=None, article=None, dynamic_fields=None,
+ attachments=None, **kwargs):
+ """Update an existing ticket.
+
+ @param ticket_id the ticket ID of the ticket to modify
+ @param ticket_number the ticket Number of the ticket to modify
+ @param ticket a ticket containing the fields to change on ticket
+ @param article a new Article to append to the ticket
+ @param dynamic_fields a list of Dynamic Fields to change on ticket
+ @param attachments a list of Attachments for a newly appended article
+ @returns the ticketID, TicketNumber
+
+
+ Mandatory : - `ticket_id` xor `ticket_number`
+ - `ticket` or `article` or `dynamic_fields`
+
+ """
+ if not (ticket_id is None):
+ kwargs['TicketID'] = ticket_id
+ elif not (ticket_number is None):
+ kwargs['TicketNumber'] = ticket_number
+ else:
+ raise ValueError('requires either ticket_id or ticket_number')
+
+ if (ticket is None) and (article is None) and (dynamic_fields is None):
+ raise ValueError(
+ 'requires at least one among ticket, article, dynamic_fields')
+ elif (article is None) and not (attachments is None):
+ raise ValueError(
+ 'Attachments can only be created for a newly appended article')
+ else:
+ if (ticket):
+ kwargs['Ticket'] = ticket
+ if (article):
+ kwargs['Article'] = article
+ if (dynamic_fields):
+ kwargs['DynamicField'] = dynamic_fields
+ if (attachments):
+ kwargs['Attachment'] = attachments
+
+ ret = self.req('TicketUpdate', **kwargs)
+ elements = self._unpack_resp_several(ret)
+ infos = {extract_tagname(i): int(i.text) for i in elements}
+ return infos['TicketID'], infos['TicketNumber']
diff --git a/otrs/ticket/template.py b/otrs/ticket/template.py
new file mode 100644
index 0000000..1214bfa
--- /dev/null
+++ b/otrs/ticket/template.py
@@ -0,0 +1,17 @@
+"""OTRS :: ticket:: template."""
+from otrs.ticket.operations import TicketCreate, TicketGet
+from otrs.ticket.operations import TicketSearch, TicketUpdate
+from otrs.session.operations import SessionCreate
+from otrs.client import WebService
+
+
+def GenericTicketConnectorSOAP(webservice_name='GenericTicketConnectorSOAP'):
+ """Return a GenericTicketConnectorSOAP Webservice object.
+
+ @returns a WebService object with the GenericTicketConnectorSOAP operations
+ """
+ return WebService(webservice_name, 'http://www.otrs.org/TicketConnector',
+ SessionCreate=SessionCreate(),
+ TicketCreate=TicketCreate(),
+ TicketGet=TicketGet(), TicketSearch=TicketSearch(),
+ TicketUpdate=TicketUpdate())
diff --git a/tests.py b/tests.py
index 9d6c4c3..23968f3 100644
--- a/tests.py
+++ b/tests.py
@@ -2,8 +2,9 @@
import os
import xml.etree.ElementTree as etree
-from otrs.client import GenericTicketConnector
-from otrs.objects import Ticket, Article
+from otrs.client import GenericInterfaceClient
+from otrs.ticket.template import GenericTicketConnectorSOAP
+from otrs.ticket.objects import Ticket, Article
REQUIRED_VARS = 'OTRS_LOGIN', 'OTRS_PASSWORD', 'OTRS_SERVER', 'OTRS_WEBSERVICE'
MISSING_VARS = []
@@ -168,21 +169,21 @@
class TestOTRSAPI(unittest.TestCase):
def setUp(self):
- self.c = GenericTicketConnector(OTRS_SERVER, OTRS_WEBSERVICE)
+ self.c = GenericInterfaceClient(OTRS_SERVER, tc=GenericTicketConnectorSOAP(OTRS_WEBSERVICE))
self.c.register_credentials(OTRS_LOGIN, OTRS_PASSWORD)
def test_session_create(self):
- sessid = self.c.session_create(user_login=OTRS_LOGIN,
- password=OTRS_PASSWORD)
+ sessid = self.c.tc.SessionCreate(user_login=OTRS_LOGIN,
+ password=OTRS_PASSWORD)
self.assertEqual(len(sessid), 32)
def test_ticket_get(self):
- t = self.c.ticket_get(1)
+ t = self.c.tc.TicketGet(1)
self.assertEqual(t.TicketID, 1)
self.assertEqual(t.StateType, 'new')
def test_ticket_get_with_articles(self):
- t = self.c.ticket_get(1, get_articles=True)
+ t = self.c.tc.TicketGet(1, get_articles=True)
self.assertEqual(t.TicketID, 1)
self.assertEqual(t.StateType, 'new')
articles = t.articles()
@@ -191,7 +192,7 @@ def test_ticket_get_with_articles(self):
self.assertEqual(articles[0].SenderType, 'customer')
def test_ticket_search(self):
- t_list = self.c.ticket_search(Title='Welcome to OTRS!')
+ t_list = self.c.tc.TicketSearch(Title='Welcome to OTRS!')
self.assertIsInstance(t_list, list)
self.assertIn(1, t_list)
@@ -206,7 +207,7 @@ def test_ticket_create(self):
Body='bla',
Charset='UTF8',
MimeType='text/plain')
- t_id, t_number = self.c.ticket_create(t, a)
+ t_id, t_number = self.c.tc.TicketCreate(t, a)
self.assertIsInstance(t_id, int)
self.assertIsInstance(t_number, int)
self.assertTrue(len(str(t_number)) >= 12)
@@ -223,11 +224,11 @@ def test_ticket_update_attrs_by_id(self):
Body='bla',
Charset='UTF8',
MimeType='text/plain')
- t_id, t_number = self.c.ticket_create(t, a)
+ t_id, t_number = self.c.tc.TicketCreate(t, a)
t = Ticket(Title='Foubar')
- upd_tid, upd_tnumber = self.c.ticket_update(ticket_id=t_id,
- ticket=t)
+ upd_tid, upd_tnumber = self.c.tc.TicketUpdate(ticket_id=t_id,
+ ticket=t)
self.assertIsInstance(upd_tid, int)
self.assertIsInstance(upd_tnumber, int)
self.assertTrue(len(str(upd_tnumber)) >= 12)
@@ -235,7 +236,7 @@ def test_ticket_update_attrs_by_id(self):
self.assertEqual(upd_tid, t_id)
self.assertEqual(upd_tnumber, t_number)
- upd_t = self.c.ticket_get(t_id)
+ upd_t = self.c.tc.TicketGet(t_id)
self.assertEqual(upd_t.Title, 'Foubar')
self.assertEqual(upd_t.Queue, 'Postmaster')
@@ -250,11 +251,11 @@ def test_ticket_update_attrs_by_number(self):
Body='bla',
Charset='UTF8',
MimeType='text/plain')
- t_id, t_number = self.c.ticket_create(t, a)
+ t_id, t_number = self.c.tc.TicketCreate(t, a)
t = Ticket(Title='Foubar')
- upd_tid, upd_tnumber = self.c.ticket_update(ticket_number=t_number,
- ticket=t)
+ upd_tid, upd_tnumber = self.c.tc.TicketUpdate(ticket_number=t_number,
+ ticket=t)
self.assertIsInstance(upd_tid, int)
self.assertIsInstance(upd_tnumber, int)
self.assertTrue(len(str(upd_tnumber)) >= 12)
@@ -262,7 +263,7 @@ def test_ticket_update_attrs_by_number(self):
self.assertEqual(upd_tid, t_id)
self.assertEqual(upd_tnumber, t_number)
- upd_t = self.c.ticket_get(t_id)
+ upd_t = self.c.tc.TicketGet(t_id)
self.assertEqual(upd_t.Title, 'Foubar')
self.assertEqual(upd_t.Queue, 'Postmaster')
@@ -277,7 +278,7 @@ def test_ticket_update_new_article(self):
Body='bla',
Charset='UTF8',
MimeType='text/plain')
- t_id, t_number = self.c.ticket_create(t, a)
+ t_id, t_number = self.c.tc.TicketCreate(t, a)
a2 = Article(Subject='UnitTest2',
Body='bla',
@@ -289,10 +290,10 @@ def test_ticket_update_new_article(self):
Charset='UTF8',
MimeType='text/plain')
- self.c.ticket_update(t_id, article=a2)
- self.c.ticket_update(t_id, article=a3)
+ self.c.tc.TicketUpdate(t_id, article=a2)
+ self.c.tc.TicketUpdate(t_id, article=a3)
- t_upd = self.c.ticket_get(t_id, get_articles=True)
+ t_upd = self.c.tc.TicketGet(t_id, get_articles=True)
arts_upd = t_upd.articles()
self.assertIsInstance(arts_upd, list)
self.assertEqual(len(arts_upd), 3)