Shopify API Orders ordering changed on May 17th 2012?

Shopify API’s orders call seems to be reversed suddenly – I am getting responses from MAX_DATE first, not the other way around.

My sync functions were expecting the latest 250 orders. I don’t see a way to specify ordering, so I have switched to using the since_id field.

Django — ‘NoneType’ object has no attribute ‘status_code’

My cache middleware was complaining about this – I thought I had a bug in another middleware that was not returning a response due to exceptions, but it was more simple: look for recent changes in your code that have altered view responses.

In my case I had added a method_decorator(cache_page) decorator to a class based view, which required I subclass the dispatch() line. Apparently, I forgot to return the super() call.

Make sure your views are returning responses!

PostgreSQL — Drop Column Not Null Constraint

The docs kept suggesting DROP CONSTRAINT constraint_name [ RESTRICT | CASCADE ] but it doesn’t appear to like that — you must pass the constraint_name without the CONSTRAINT keyword. At least on my 8.4.

Don’t do this as the docs might suggest..

ALTER TABLE <table> ALTER COLUMN <column> DROP CONSTRAINT not null;

Do this:

ALTER TABLE <table> ALTER COLUMN <column> DROP not null;
ALTER TABLE my_table ALTER COLUMN my_column DROP not null;

Django — Override One Specific Field Widget in Admin

formfield_overrides is just too global and redefining form fields has many drawbacks (loss of original field arguments for example and ModelForm magic). What do we do? I keep forgetting about the ModelForm meta class widget attribute.

The ModelForm meta class takes a widget attribute which is a map of fieldnames to widgets:

class OrderForm(forms.ModelForm):
    class Meta:
        model = Order
        widgets = {
            'activity': forms.Textarea(attrs={'disabled': True}),
            'log': forms.Textarea(attrs={'disabled': True}),
        }

class MyAdmin(admin.ModelAdmin):
    form = OrderForm

Photoshop OSX Sluggish Text and No Drag and Drop

I’ve been living with this for a while… typing 20 characters of text would take a minute, and more importantly, I could NOT drag and drop layers from one document to another.

I figured I’d fix the text problem first which happens to cure the drag and drop issue too!

I found a help article that told me to run OSX Font Book’s font validation tool.

It analyzed all of my fonts, found 10 that were troublesome, and provided an easy way to remove them all from my system after which my photoshop types text like a champ!

http://helpx.adobe.com/photoshop/kb/troubleshoot-fonts-photoshop-cs5.html

Python xlwt: writing excel files

To write excel files with Python, first download / install xlwt.

pip install xlwt

A demo with common operations should help more than a write up:

Create workbook and sheet

import xlwt
workbook = xlwt.Workbook() 
sheet = workbook.add_sheet("Sheet Name") 

Write a cell

sheet.write(0, 0, 'foobar') # row, column, value

Apply styles to a cell (bold)

style = xlwt.easyxf('font: bold 1')
sheet.write(0, 0, 'foobar', style)

Set width of column

To set the widths, you must set the width attribute to 256*NUM_CHARS where 256 is equal to the width of the zero character.

sheet.col(0).width = 256 * (len(key) + 1) 
# set width.. 256 = 1 width of 0 character

Apply multiple styles to a cell

Note that you are limited to 4k styles per document, meaning you should not initialize a style for every cell but re-use them (read below for simple cache solution).

style = xlwt.easyxf('font: bold 1, color red;'))
sheet.write(0, 0, 'foobar', style)

Apply currency style

To set currency, add the keyword argument num_format_str to the easyxf function or set the attribute on the returned style object.

style = easyxf(num_format_str='$#,##0.00')
# or set it directly on the style object
style = easyxf('font: bold 1')
style.num_format_str = '$#,##0.00'
sheet.write(0, 0, '100.00', style)

Write excel formulas

Writing formulas is trivial with xlwt.Formula

sheet.write(0, 0, xlwt.Formula('HYPERLINK("http://yujitomita.com"; "click me")'))

Save

workbook.save("foobar.xls") 
# done!

Gotchas

Here are some things that “got” me good (took some troubleshooting).

Can’t override cells

I actually like this feature – it prevents cells from being overridden, so I’ve figured out on more than one occasion my script was failing. Why would you be overwriting a cell anyways?

# to overwrite cells, create the sheet with kwarg cell_overwrite_ok
workbook.add_sheet('foobar', cell_overwrite_ok=True) 

Valid sheet name

  • Sheets are only valid if under 31 characters
  • They can’t contain characters such as ‘:’, ‘/’. More to come I’m sure…

Style 4k limit per document

Applying styles has been made trivial, if you know the rules.

There’s a 4k limit on styles defined in a document, so I created a cached easyxf function which at least tries to pull a definition from cache before creating a new style.

class MyClass(object):
	kwd_mark = object()
	def cached_easyxf(self, string='', **kwargs):
		if not hasattr(self, '_cached_easyxf'):
			self._cached_easyxf = {}
		key = (string,) + (self.kwd_mark,) + tuple(sorted(kwargs.items()))
		return self._cached_easyxf.setdefault(key, xlwt.easyxf(string, **kwargs))

Easyxf string format

I just did a bit of experimentation to figure out the common formats.

For example, the string format appears to accept an array of key value pairs separated by a space.

‘KEY: KEY-VALUE VALUE, KEY-VALUE VALUE; KEY2: KEY-VALUE2 VALUE2’

sheet.write(0, 0, xlwt.easyxf('font: bold 1')) # bold
sheet.write(0, 0, xlwt.easyxf('font: bold 1, color: blue, underline single')) 

django formfield_for_dbfield : __init__ unexpected keyword argument ‘request’

Quick post:

formfield_for_dbfield examples floating around out there do not work because a lot of them directly call `db_field.formfield(**kwargs)`.

Either remove that call and let the super call take care of it (such as below) or remove `request` from the kwargs by calling kwargs.pop(‘request’)

It turns out the super call now removes the request kwarg before moving further in processing, so if you manually call db_field.formfield(**kwargs) your code will have this extra keyword.

def formfield_for_dbfield(self, db_field, **kwargs): 
    if db_field.attname == 'long_description':
         kwargs['widget'] = CLEditorWidget()
    return super(ProductAdmin, self).formfield_for_dbfield(db_field, **kwargs)

I believe this hook isn’t supposed to be used even, as it’s not really in the docs.

I use it because formfield_overrides is global and doesn’t have per-field granularity and formfield_for_FK/M2M only works for relationships.

If I’m missing a better hook, just let me know. I haven’t really dug around as this is perfectly acceptable.

Sorl Thumbnail Convert PNG to JPEG with Background Color

The benefits of PNGs are obvious: it’s lossless and it supports transparency (alpha).

Unfortunately, that means these files are humongous. As PNGs get more complex, every pixel gets recorded so a product shot might be 1000KB as PNG, and 50KB as a JPEG looking not all that much worse.

If you try to convert a PNG to a JPEG via Sorl thumbnail library, it will by default use a white background, and for some reason causes artifacting as well on partial alphas such as shadows.

Ideally, we have a super high quality PNG that can be converted and optimized to a JPG on the fly, so I created a PIL sorl thumbnail engine that accepts a new template tag argument: “background” – which is an RGB color that will be applied as the background color to an RGBA image source.

Apparently, my solution is the simplest, and fastest of the RGBA->JPG methods described at this stack overflow question.

The Code: Converting Transparent PNGs to JPEGs with Background Colors

"""
Sorl Thumbnail Engine that accepts background color
---------------------------------------------------

Created on Sunday, February 2012 by Yuji Tomita
"""
from PIL import Image, ImageColor
from sorl.thumbnail.engines.pil_engine import Engine


class Engine(Engine):
	def create(self, image, geometry, options):
		thumb = super(Engine, self).create(image, geometry, options)
		if options.get('background'):		
			try:
				background = Image.new('RGB', thumb.size, ImageColor.getcolor(options.get('background'), 'RGB'))
				background.paste(thumb, mask=thumb.split()[3]) # 3 is the alpha of an RGBA image.
				return background
			except Exception, e:
				return thumb
		return thumb

Now, just modify your thumbnail engine setting

THUMBNAIL_ENGINE = 'path.to.Engine'

Usage

{% thumbnail my_file "100x100" format="JPEG" background="#333333" as thumb %}
   <img src="{{ thumb.url }}" />
{% endthumbnail %}

Done!

Conclusion

Now, we can store/upload one copy of a perfect PNG and have the website automatically generate an optimized JPEG regardless of the situation. Seriously amazing.

Need a smaller PNG? done.
Need a smaller JPEG with background color black? done.

https://gist.github.com/1920535

S3 and Django Staticfiles collectstatic command upload only changed files

I just revisited a problem getting S3 and collectstatic to play nicely via django-storages.

I spent an hour wondering what had changed in django or django-storages that started to force the collectstatic command to always upload all images.

I did a line by line code comparison between my two django and storages installations and couldn’t find anything odd.

I started to google more.

Hello, me!

I ended up on this site: http://c4urself.posterous.com/djangos-collectstatic-with-s3boto which actually linked to a few of my original posts.

For the record this happens to me on a daily basis (my memory sucks) but this was rather unexpected in the vastness of the world wide internets.

You MUST install python-dateutil==1.5

Collectstatic will silently fail trying to detect the modified time of the S3 files, and consequently will always upload a new file. Since there was no error, I had no idea something was failing until I read the above post which pointed to the specific function (mentioned by me originally, apparently).

If your collectstatic command is always uploading all files, make sure you have python-dateutil==1.5 installed!