Repository: translate/pootle

Issue:
Is it possible to use the REST API to create and update a Project, with related resources, like Store, Translation Project, and Unit? 
I have spent considerable time now, trying to create a simple Project instance, using the REST API, but with no success so far.

Installed packages (pip freeze, with Python version 2.7.6):

```
Django==1.6.7
Pootle==2.6.0-rc2
South==1.0
argparse==1.2.1
cssmin==0.2.0
diff-match-patch==20121119
django-allauth==0.16.1
django-assets==0.10
django-contact-form==1.0
django-taggit==0.11.1
django-tastypie==0.12.0
django-voting==0.1
lxml==3.4.0
oauthlib==0.6.3
python-dateutil==2.2
python-memcached==1.53
python-mimeparse==0.1.4
python-openid==2.2.5
requests==2.4.3
requests-oauthlib==0.4.1
six==1.8.0
```

I use the the slumber library to access the API.  The following code

``` python
api = slumber.API('http://localhost:8000/api/v1/', auth=('admin', 'admin'))
api.projects.get()
```

returns the expected 

```
{'meta': {'limit': 1000,
  'next': None,
  'offset': 0,
  'previous': None,
  'total_count': 2},
 'objects': [{'backlink': 'http://localhost:8000/projects/terminology/',
   'checkstyle': 'terminology',
   'code': 'terminology',
   'description': u'',
   'fullname': 'Terminology',
   'ignoredfiles': u'',
   'localfiletype': 'po',
   'resource_uri': '/api/v1/projects/1/',
   'source_language': '/api/v1/languages/2/',
   'translation_projects': ['/api/v1/translation-projects/2/',
    '/api/v1/translation-projects/3/',
    # […]
    '/api/v1/translation-projects/62/',
    '/api/v1/translation-projects/63/'],
   'treestyle': 'auto'},
  {'backlink': 'http://localhost:8000/projects/tutorial/',
   'checkstyle': 'standard',
   'code': 'tutorial',
   'description': '<div dir="ltr" lang="en">Tutorial project where users can play with Pootle and learn more about translation and localisation.<br>For more help on localisation, visit the <a href="http://docs.translatehouse.org/projects/localization-guide/en/latest/guide/start.html">localisation guide</a>.</div>',
   'fullname': 'Tutorial',
   'ignoredfiles': u'',
   'localfiletype': 'po',
   'resource_uri': '/api/v1/projects/2/',
   'source_language': '/api/v1/languages/2/',
   'translation_projects': ['/api/v1/translation-projects/1/',
    '/api/v1/translation-projects/28/',
    '/api/v1/translation-projects/33/',
    '/api/v1/translation-projects/35/',
    '/api/v1/translation-projects/37/',
    '/api/v1/translation-projects/46/'],
   'treestyle': 'auto'}]}
```

While attempting the following code: 

``` python
fields = {
 'checkstyle': 'standard',
 'code': 'test01',
 'description': 'Another test.',
 'fullname': 'Test #01',
 'ignoredfiles': u'',
 'localfiletype': 'po',
 'source_language': '/api/v1/languages/2/',
 'translation_projects': [],
 'treestyle': 'auto',
}

api.projects.post(fields)
```

returns a `HttpServerError: Server Error 500: http://localhost:8000/api/v1/projects/`, which upon closer tracing and debugging seems to be caused by the line `tastypie/authorization.py:190`: 

```
        if not bundle.request.user.has_perm(permission):
```

causing an `AttributeError: 'User' object has no attribute 'has_perm'`.  Have I discovered a bug, or am I simply doing something wrong?

If someone could show me some working code examples of how to do this, I would be very happy.



Discussion:
Can you share a traceback?

I haven't checked the following, but it was picked from my notes and I know it worked in the past:

``` python
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import logging

import slumber


api = slumber.API('http://localhost:8000/api/v1/', auth=('admin', 'admin'))

project_data = {
    'code': 'test01',
    'fullname': 'Test #01',
    'description': 'Another test.',
    'source_language': '/api/v1/languages/2/',
    'translation_projects': [],
}

try:
    new_proj = api.projects.post(project_data)
except slumber.exceptions.HttpServerError:
    logging.exception("Some problem occurred while trying to interact "
                      "with the Pootle API. Aborting.")
```

I had to lurk out a _server-side_ traceback from PyCharm, and I hope this will do:

``` python
  File "<string>", line 1, in <module>
  File "/usr/lib/python2.7/threading.py", line 783, in __bootstrap
    self.__bootstrap_inner()
  File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 763, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib/python2.7/SocketServer.py", line 593, in process_request_thread
    self.finish_request(request, client_address)
  File "/usr/lib/python2.7/SocketServer.py", line 334, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/pootle_env/local/lib/python2.7/site-packages/django/core/servers/basehttp.py", line 126, in __init__
    super(WSGIRequestHandler, self).__init__(*args, **kwargs)
  File "/usr/lib/python2.7/SocketServer.py", line 649, in __init__
    self.handle()
  File "/usr/lib/python2.7/wsgiref/simple_server.py", line 124, in handle
    handler.run(self.server.get_app())
  File "/usr/lib/python2.7/wsgiref/handlers.py", line 85, in run
    self.result = application(self.environ, self.start_response)
  File "/pootle_env/local/lib/python2.7/site-packages/django/contrib/staticfiles/handlers.py", line 67, in __call__
    return self.application(environ, start_response)
  File "/pootle_env/local/lib/python2.7/site-packages/django/core/handlers/wsgi.py", line 206, in __call__
    response = self.get_response(request)
  File "/pootle_env/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 112, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/pootle_env/local/lib/python2.7/site-packages/django/db/transaction.py", line 371, in inner
    return func(*args, **kwargs)
  File "/pootle_env/local/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 57, in wrapped_view
    return view_func(*args, **kwargs)
  File "/pootle_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 201, in wrapper
    response = callback(request, *args, **kwargs)
  File "/pootle_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 432, in dispatch_list
    return self.dispatch('list', request, **kwargs)
  File "/pootle_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 464, in dispatch
    response = method(request, **kwargs)
  File "/pootle_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 1340, in post_list
    updated_bundle = self.obj_create(bundle, **self.remove_api_resource_names(kwargs))
  File "/pootle_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 2104, in obj_create
    return self.save(bundle)
  File "/pootle_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 2244, in save
    self.authorized_create_detail(self.get_object_list(bundle.request), bundle)
  File "/pootle_env/local/lib/python2.7/site-packages/tastypie/resources.py", line 621, in authorized_create_detail
    auth_result = self._meta.authorization.create_detail(object_list, bundle)
  File "/pootle_env/local/lib/python2.7/site-packages/tastypie/authorization.py", line 190, in create_detail
    if not bundle.request.user.has_perm(permission):
```

Running your script, I experience the same server-side exception.

For completeness, I include my ~/.pootle/pootle.conf:

``` python
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Sample configuration file.

This file includes the settings that administrators will likely change.
You can find the defaults in the ``*.conf`` files for more advanced settings.

In order for this configuration changes to take effect, bear in mind that you
need to change the extension of this file from ``.conf.sample`` to ``.conf``.
"""


#
# Base
#

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'UTC'

# Make this unique, and don't share it with anybody.
SECRET_KEY = 'C/sxZT4V<it\'s supposed to be a secret>yRyturQ='

# A list of strings representing the host/domain names that this Pootle server
# can serve. This is a Django's security measure. More details at
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']


#
# Backends
#

# Database backend settings
DATABASES = {
    'default': {
        # Replace 'sqlite3' with 'postgresql_psycopg2', 'mysql' or 'oracle'.
        'ENGINE': 'django.db.backends.sqlite3',
        # Database name or path to database file if using sqlite3.
        'NAME': working_path('dbs/pootle.db'),
        # Not used with sqlite3.
        'USER': '',
        # Not used with sqlite3.
        'PASSWORD': '',
        # Set to empty string for localhost. Not used with sqlite3.
        'HOST': '',
        # Set to empty string for default. Not used with sqlite3.
        'PORT': '',
        # See https://docs.djangoproject.com/en/1.6/topics/db/transactions/
        # required for Django 1.6 + sqlite
        'ATOMIC_REQUESTS': True,
    }
}


# Cache Backend settings

# By default we use Django's local-memory cache which is only suitable
# for small deployments.
#
# If you are going to deploy a real server memcached is preferred. For more
# information, check
# http://docs.djangoproject.com/en/dev/topics/cache/#setting-up-the-cache
CACHES = {
   'default': {
       'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
       'LOCATION': '127.0.0.1:11211',
   }
}

POOTLE_LOG_DIRECTORY = '/var/log/pootle'

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'action': {
            'format' : "[%(asctime)s]\t%(message)s",
            'datefmt' : "%Y-%m-%dT%H:%M:%S"
        },
    },
    'handlers': {
        # Log to a text file that can be rotated by logrotate
        'log_action': {
            'class': 'logging.handlers.WatchedFileHandler',
            'filename': os.path.join(POOTLE_LOG_DIRECTORY,
                                     'pootle-activity.log'),
            'formatter':'action',
        },
    },
    'loggers': {
        'action': {
            'handlers': ['log_action',],
            'level': 'INFO',
            'propagate': False,
        },
    },
}

#
# Site
#

# This Pootle server admins
ADMINS = (
    # ('Your Name', 'your_email@domain.com'),
)

# Set this to False to disable user registration, admins will still be
# able to create user accounts.
CAN_REGISTER = True

# Mail settings

# Address used for messages sent by Pootle.
DEFAULT_FROM_EMAIL = 'info@YOUR_DOMAIN.com'

# Address to receive messages sent by contact form.
CONTACT_EMAIL = 'info@YOUR_DOMAIN.com'
CAN_CONTACT = True

# Email address to report string errors to, unless a report email was set for
# the project for which the string error is being reported.
POOTLE_REPORT_STRING_ERRORS_EMAIL = 'string_errors_manager@YOUR_DOMAIN.com'

# Mail server settings

# By default Pootle uses the SMTP server on localhost. If the server is not
# configured for sending emails, uncomment and use these settings to setup an
# external outgoing SMTP server.

# Example for Google as an external SMTP server
#EMAIL_HOST_USER = 'USER@YOUR_DOMAIN.com'
#EMAIL_HOST_PASSWORD = 'YOUR_PASSWORD'
#EMAIL_HOST = 'smtp.gmail.com'
#EMAIL_PORT = 587
#EMAIL_USE_TLS = True


#
# Translation
#

# The directory where the translation files are kept
PODIRECTORY = working_path('po')

# Two-tuple defining the markup filter to apply in certain textareas.
#
# - Accepted values for the first element are 'textile', 'markdown',
#   'restructuredtext' and None.
# - The second element should be a dictionary of keyword arguments that will be
#   passed to the markup function.
#
# IMPORTANT: If you want to use one of these markup filters you must install on
# your own the required packages.
#
# Examples:
#    MARKUP_FILTER = (None, {})
#    MARKUP_FILTER = ('markdown', {'safe_mode': 'escape'})
#    MARKUP_FILTER = ('restructuredtext', {
#                         'settings_overrides': {
#                             'report_level': 'quiet',
#                         }
#                     })
MARKUP_FILTER = (None, {})

# Set the backends you want to use to enable translation suggestions through
# several online services. To disable this feature completely just comment all
# the lines to set an empty list [] to the MT_BACKENDS setting.
#
# The second parameter for each backend option is the API key, which will
# be used in case the service supports using an API key.
#
# Available options are:
# 'APERTIUM': Apertium service.
#             For this service you need to set the API key.
#             Get your key at http://api.apertium.org/register.jsp
# 'GOOGLE_TRANSLATE': Google Translate service.
#             For this service you need to set the API key.
#             Note that Google Translate API is a paid service
#             See more at http://code.google.com/apis/language/translate/v2/pricing.html
#
MT_BACKENDS = [
#        ('APERTIUM', ''),
#        ('GOOGLE_TRANSLATE', ''),
]

# Set the backends you want to use to allow web-based lookup. To disable this
# feature completely just comment all the lines to set an empty list [] to the
# LOOKUP_BACKENDS setting.
LOOKUP_BACKENDS = [
        'wikipedia',
]


# Certain username and names should not be included in the contributors page
CONTRIBUTORS_EXCLUDED_NAMES = (
#        'sorryjusttesting',
#        'testest',
)

# Certain projects should not be included in the contributors page
CONTRIBUTORS_EXCLUDED_PROJECT_NAMES = (
#        "Testing, please don't work here",
)

POOTLE_ENABLE_API = True
DEBUG = True
TASTYPIE_FULL_DEBUG = True
```

What version of pootle is this?

From the original post: `Pootle==2.6.0-rc2`

Missed that. Will look into it, thanks.

Hi @jleclanche,

Have you had any time to look into this issue?  Do you acknowledge that it's a bug, and if so do you know when you'll be able to fix it?

Sorry, haven't had time to look at it yet. Will look at it tomorrow.


Resolving patch:
diff --git a/pootle/apps/accounts/models.py b/pootle/apps/accounts/models.py
--- a/pootle/apps/accounts/models.py
+++ b/pootle/apps/accounts/models.py
@@ -22,7 +22,7 @@
 import re
 from hashlib import md5
 
-from django.contrib.auth.models import AbstractBaseUser
+from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
 from django.core.mail import send_mail
 from django.core.urlresolvers import reverse
 from django.core.validators import RegexValidator
@@ -43,7 +43,7 @@
 from .managers import UserManager
 
 
-class User(AbstractBaseUser):
+class User(AbstractBaseUser, PermissionsMixin):
     """The Pootle User.
 
     ``username``, ``password`` and ``email`` are required. Other fields
@@ -67,9 +67,6 @@ class User(AbstractBaseUser):
     is_active = models.BooleanField(_("active"), default=True,
         help_text=_("Designates whether this user should be treated as "
                     "active. Unselect this instead of deleting accounts."))
-    is_superuser = models.BooleanField(_("superuser status"), default=False,
-        help_text=_("Designates that this user has all permissions without "
-                    "explicitly assigning them."))
 
     date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
 


Test patch: