#should contain doctest sequence for the whole app
"""
The imadj app is supposed to be a strict container for all bitmaps
in ReportLab's PDF-making apps.  Try to document all APIs below.

Dummy test images can be found in the 'testimages' directory.
We'll prepare a few paths which are not relevant to the API first.

>>> import os
>>> from rldjango.apps import imadj
>>> testImageDir = os.path.join(os.path.dirname(imadj.__file__), 'testimages')
>>> logoPath = os.path.join(testImageDir, 'logo_bg.gif')
>>> pdfPath = os.path.join(testImageDir, 'pdfs', 'colors.pdf')
>>> os.path.isfile(logoPath)
True
>>> cardPath = os.path.join(testImageDir, 'cards', '2c.gif')
>>> cardPath2 = os.path.join(testImageDir, 'cards', '3c.gif')
>>> os.path.isfile(cardPath)
True

We require all images to match a specified size.  So we'd better
define some sizes first.   These three will help...

>>> from rldjango.apps.imadj.models import Image, Size, Tag
>>> sLogo = Size(id="logo", width=182, height=113, format="gif,jpg,png")
>>> sLogo.save()
>>> sBanner = Size(id="banner", width=838, height=19, format="gif,jpg,png")
>>> sBanner.save()
>>> sCard = Size(id="card", width=73, height=97, format="gif")
>>> sCard.save()
>>> sJpgCard = Size(id="jpg-card", width=73, height=97, format="jpg")
>>> sJpgCard.save()
>>> sPdf = Size(id='pdf', width=0, height=0, format='pdf')
>>> sPdf.save()

Creating images
---------------

You can programmatically load images from a file or URL
with the 'new' method, and its ID will be returned.  The id
will never change for a given image; it consists of
'RLIMG_', then a hash of the content, then 'jpg' or
whatever the extension is.

>>> img = Image.new(path=logoPath, size=sLogo)
>>> img.id
'RLIMG_cf45b9e88267d199b9cd7b9f79d0d240.GIF'
>>> img.description
''
>>> img.description = 'our glorious logo, loaded from a file'
>>> img.save()

Loading from a missing file raises the obvious exception...

>>> img = Image.new(path='/foo/nowhere', size=sLogo)
Traceback (most recent call last):
IOError: [Errno 2] No such file or directory: '/foo/nowhere'

And from a bad URL...

>>> img3 = Image.new(url='http://www.example.com/some.gif', size=sBanner)
Traceback (most recent call last):
HTTPError: HTTP Error 404: Not Found


Now for one from a URL - if this fails, check our web site has not changed ;-)

>>> img2 = Image.new(url='http://www.reportlab.com/media/base/slogan_img.gif', size=sBanner)
>>> img2.id
'RLIMG_1c63217cc86976dc05ac3428ed9e9ae8.GIF'



Repeatedly loading the same image should leave the same record in the database
unmodified (to avoid overwriting old descriptions and tags), but appear to
return successfully.

>>> img4 = Image.new(path=logoPath, size=sLogo)
>>> img4.id
u'RLIMG_cf45b9e88267d199b9cd7b9f79d0d240.GIF'
>>> img4.description     #note that this comes back from DB as unicode.
u'our glorious logo, loaded from a file'
>>> str(img) == str(img4)
True

Adding pdfs
-----------

>>> pdf = Image.new(path=pdfPath)
>>> pdf.size
<Size: pdf>
>>> pdf.info
"{'box_art': [55.209949999999999, 78.082639999999998, 540.06560000000002, 763.80709999999999], 'box_media': [0, 0, 595.27560000000005, 841.88980000000004], 'box_crop': [14.88189, 21.047239999999999, 580.39369999999997, 820.84249999999997], 'box_trim': [42.450589999999998, 60.037260000000003, 552.82500000000005, 781.85249999999996], 'box_bleed': [29.019690000000001, 41.04213, 566.2559, 800.84760000000006]}"

More validation
---------------

We try to raise helpful exceptions.  To save forensics in big nightly
updates, an exception should indicate the file or URL it had trouble with.

Trying to create an image with an incorrect size should raise an exception
>>> img = Image.new(path=cardPath, size=sLogo)
Traceback (most recent call last):
ImageException: Image 2c.gif: Incorrect width for this image.  Expected 182 but got 73
>>> img = Image.new(path=cardPath2, size=sJpgCard)
Traceback (most recent call last):
ImageException: Image 3c.gif: Incorrect format for this image.  Expected one of ['JPEG'] but got GIF

You can omit the size if there is exactly one size spec which matches...

>>> ace_of_spades = os.path.join(testImageDir, 'cards', 'as.gif')
>>> img = Image.new(path=ace_of_spades)
>>> img.size
<Size: card>

...but otherwise it will complain and require a size...

>>> img = Image.new(path=os.path.join(testImageDir, 'purple_btn.gif'))
Traceback (most recent call last):
ImageException: Image does not match any known sizes: purple_btn.gif



TODO:  test resaving sizes, make sure it rechecks every image.




Tags
----
Tried to use django-tagging, but it requires that each tagged object
has a numeric ID, and our images don't. We want to avoid muddling
image tags with other kinds of tags so have to do it ourselves.

  
>>> ace_of_spades = os.path.join(testImageDir, 'cards', 'as.gif')
>>> img = Image.new(path=ace_of_spades)
>>> img.tag_text
''
>>> img.tag_text = 'ace spades red'
>>> img.tag_text   #alpha by default
'ace red spades'
>>> img.tag_text = 'best card, ace, red, spades'   #commas should work too
>>> img.tag_text
'ace best_card red spades'


Let's tag the whole deck of cards...

>>> from rldjango.apps.imadj import tests
>>> tests.loadAllTheCards()  #doctest: +ELLIPSIS
Created 54 cards




Testing the admin
-----------------

The admin has been overridden to make images manageable at a basic
level.  You can navigate to Images, click 'add', and go through a
one-step or two-step process to load it in.  The first step is a
simple file upload.  If it autodetects the size, it gets added
immediately.  If not, an edit screen is shown.


First we need a user...

>>> from django.test.client import Client
>>> from django.contrib.auth.models import User
>>> u, created = User.objects.get_or_create(username="fred", is_staff=True, is_superuser=True)
>>> u.set_password("secret")
>>> u.save()
>>> 
>>> c = Client()
>>> c.login(username="fred", password="secret")
True
>>> response = c.get('/admin/')
>>> response.status_code
200
>>> 'Tags' in response.content  #are we in?
True


Test our autocomplete:
>>> response = c.get('/imagegallery/autocomplete/tags/')
>>> 'queen' in response.content
True
>>> response = c.get('/imagegallery/autocomplete/tags/?q=ack')
>>> print response.content
back
black
jack





Verify the add URL brings up a file upload...

>>> resp = c.get('/admin/imadj/image/add/')
>>> '<input type="submit" value="Upload"' in resp.content
True



We add one which matches a known size.  Should go straight in.
>>> f = open(os.path.join(testImageDir, 'cards', 'qh.gif'), 'rb')
>>> resp = c.post('/admin/imadj/image/add/', {'image': f})
>>> resp.status_code   
200
>>> resp = c.get('/admin/imadj/image/')
>>> #it should now show in the admin.
>>> 'RLIMG_311c2b24dfda0d879feba1cb5a972265.GIF' in resp.content
True


Tests of REST APIs
-------------------

Generic way to write Javascript functions that modify stuff.

>>> response = c.get('/imagegallery/api/tag')
>>> response.status_code
200

>>> #POST to collection URL should create a tag and redirect to it.
>>> response = c.post('/imagegallery/api/tag', {'id': 'testtag', 'description': 'Tag for testing purposes'})
>>> response.status_code   # is this right?
302
>>> response['Location']
'http://testserver/imagegallery/api/tag/testtag/'
>>> response = c.get('/imagegallery/api/tag/testtag')
>>> 'testtag' in response.content
True
>>> 'Tag for testing purposes' in response.content
True
>>> Tag.objects.get(id='testtag').id   #is it in the database?
u'testtag'



>>> response = c.delete('/imagegallery/api/tag/testtag')
>>> response = c.get('/imagegallery/api/tag/testtag')
>>> response.status_code
404

>>> Tag.objects.filter(id='testtag').count()
0


REST security
-------------

The REST APIs will allow read-only access to registered
classes, and a 404 for others.  So read access is the default.

If logged in, you still need permission to edit stuff.
The user 'u' has this.   Create a user 'u2' who can
log in but cannot add/modify/delete...

>>> u2, created = User.objects.get_or_create(username="freda")
>>> u2.set_password("secret")
>>> u2.save()

Logged-in client can GET stuff

>>> c3 = Client()
>>> c3.login(username="freda", password="secret")
True
>>> c3.get('/imagegallery/api/tag/hearts').status_code
200
>>> response = c3.post('/imagegallery/api/tag', {'id': 'testtag', 'description': 'Tag for testing purposes'})
>>> response.status_code   #should be rejected
403
>>> response.content
'You do not have rights to add tag.'
>>> from django.contrib.auth.models import Permission
>>> add_tag = Permission.objects.get(codename='add_tag', content_type__app_label='imadj')
>>> change_tag = Permission.objects.get(codename='change_tag', content_type__app_label='imadj')
>>> delete_tag = Permission.objects.get(codename='delete_tag', content_type__app_label='imadj')
>>> u2.user_permissions.add(add_tag)
>>> u2.user_permissions.add(change_tag)
>>> u2.user_permissions.add(delete_tag)
>>> u2.save()

Should now be able to create tags and update tag details
>>> response = c3.post('/imagegallery/api/tag', {'id': 'testtag', 'description': 'Tag for testing purposes'})
>>> response.status_code   #should be accepted and redirect to new tag's URL
302
>>> response['Location']
'http://testserver/imagegallery/api/tag/testtag/'

>>> response = c3.post('/imagegallery/api/tag/testtag', {'description': 'modified'})
>>> response.status_code   #should be accepted and redirect to new tag's URL
200
>>> response.content
'updated'
>>> response = c3.delete('/imagegallery/api/tag/testtag')
>>> response.status_code   
200








Andy's notes:
============

- test suite should create a different folder for output.  (Is there a hook
to do this when starting manage.py tests?)


PDF loading functionality - PDFs are more compled than images.
Must see a preview when loading, and be able to adjust bounding boxes
or see FAQs on Acrobat/Illustrator options.

"""




def loadAllTheCards():
    """Utility to load in a playing card deck, along with tags.
    
    It can be handy to run this in a development database so we put it
    outside the docstrings.
    """


    import os
    from rldjango.apps import imadj
    from models import Image, Tag, Size
    
    sCard, created = Size.objects.get_or_create(id='card',
                                                width=73,
                                                height=97,
                                                format='gif')
    testImageDir = os.path.join(os.path.dirname(imadj.__file__), 'testimages')
    
    values = 'a23456789tjqk'
    suits = 'hdcs'
    valueNames = 'ace two three four five six seven eight nine ten ' \
        'jack queen king' .split()
    suitNames = 'hearts diamonds clubs spades'.split()
    count = 0
    for i, value in enumerate(values):
        for j, suit in enumerate(suits):
            valueName = valueNames[i]
            suitName = suitNames[j]
            if suit in 'hd':
                colourName = 'red'
            else:
                colourName = 'black'
            cardFileName = os.path.join(testImageDir, 'cards', '%s%s.gif' % (value, suit))
            img = Image.new(path=cardFileName, size=sCard)
            tag_text = '%s %s %s' % (valueName, suitName, colourName)
            if value in 'jqk':
                tag_text = tag_text + ' face'
            img.tag_text = tag_text
            img.description = '%s of %s' % (valueName, suitName)
            img.save()
            count += 1
            #print 'created ', img.description
            
    for extra in ['joker','back']:
        cardFileName = os.path.join(testImageDir, 'cards', '%s.gif' % extra[0])
        img = Image.new(path=cardFileName, size=sCard)
        img.tag_text = extra
        #print 'created', extra
        count += 1
    print "Created %d cards" % count
    


