Monday, March 14, 2016

The they don't teach you in school a.k.a. Any idiot can follow a recipe

Due to the accelerated nature of the curriculum, there was an awful lot of "don't worry about it, just follow the cookbook".  Because the teachers knew what they were doing, the cookbook worked.  The problem: you can give any idiot a recipe for pie, and you'll usually get a decent pie.  The challenge: adapt the recipe to a new filling, a new crust, a different stove...

This bothered me at the time, and it drove some of my cohort to distraction.  The simple truth, though, is that a bootcamp just doesn't have time to cover every possible contingency and the background.  The more I work on computering, the more I believe in the "Become an expert programmer in 10 years" paradigm.  And you know what?  I'm fine with that.  I got into this to have a project that would take me a long time to mastery (but would have measurable milestones along the way.)

The things I did get from bootcamp are a) a curated list of topics that I should focus on, b) familiarity with how things should look when they're working; and c) enough knowledge to understand the reference I do find.

Part a) is important because there's an unlimited ocean of possible paths out there.  I learned a foundational technology (python) and one solid implementation (django) deployed on the web via heroku.  I can modify these, and improve them, and refer back to them as I improve.

I also have other things that I need to keep working on: javascript (including Node, Backbone and Angular libraries), server archtecture, and so on.

Part b) (knowing what a working prototype should be able to do) is vital.  Knowing something is possible is more than half of getting it work yourself.  It's vastly harder to do something when you don't know what the finished product should look like.

Part c) is probably the most valuable.  Before gocode, the Python documentation was over my head.  I usually had to find a Stack Overflow answer at an ELI5 (explain it like I'm 5) level.  Having the self-confidence to find answers is so useful...I'm really beginning to believe I can do this.

This all comes up because I spent a lot of time today wrestling not with python, or django, or JS...but with OSX.  I started following the heroku tutorial on setting up a new pyton app (to see what I'd missed in following the cookbook when setting up catfinder).  In the process, I got a an error in that my installation into a virtual environment couldn't find my PostgreSQL install.

End result: I know a lot more about paths, config scripts, and OSX than I did a few hours ago.  That wasn't what I set out to learn, but knowing how your own computer works is pretty essential part of making everything else work.


Tuesday, March 8, 2016

Wrap up, and life post codeschool

That last week was a blur.

The net result, however, was that I have a mostly-functional friendlycatfinder running on heroku.  I got enough done for a brief final presentation. Highlights:

1. Google location works either with address or autolocate
2. The main display page uses a mix of django templating and ajax calls (via a views.py method written just for them) to fill out a bootstrap carousel template.
3. The JSON object returned via the ajax call is used to build detailed information for each cat.
4. My django templates base.html is split into three parts: a <head> that contains the static files and script links used by every page, a <body> section that includes the navbar code that appears on every page and a footer section for each page. The blocks extend in such a way that each particular page can have elements in the header that unique to that page, as well as the body elements.

So now I'm home in Dallas, fighting off a (presumably) stress-induced cold, and thinking about the future.  This week, I'm visiting co-working spaces to find a good fit (and good networking opportunities), getting an updated CV and linked-in profile up, and finishing some projects from code school for the portfolio.

I'm also debating restarting the catfinder project to do it properly, with a better url scheme and class-based views.  I'd also like to make the whole experience of using it simpler by making the landing page ask for a photo upload, getting exif data if possible for location information, having login appear via a modal if neccessary, and making sure that everything is mobile-centric from the very beginning.

Balanced against that is the desire to have a capstone project that is fully functional, if not quite optimal.

Eesh.

Still, it's exciting to be out.  I feel like I'm ready to start contributing on new things.  Stay tuned.  I intend to keep this updated regularly.

Cheers!

Tuesday, March 1, 2016

w8d1&2 - Heroku, javascript and a slightly functional catfinder

So the super-alpha version (can code have a negative version number?) of www.friendlycatfinder.com is up on heroku.  It's functional as far as it goes (i.e. you can add cat sightings and see a list of cats in a particular area).  It's ugly as hell, and it's still missing a lot of polish.  There's no guarantee from minute to minute that it will still be working, as I still have a ton of mods to do.

I'll also admit that the heroku spin-up was entirely done by the instructors.  I have a detailed walk-through (with annotations!), but I can't claim to be capable of this on my own.

But still...

Today was all about getting some front-end styling in place.  I took a carousel bootstrap template that I'd downloaded previously and started playing with it.  I have it partially working in development.

And just as I type that, I go back to look and the carousel is not turning.  Sigh.

Still, I've gotten over the hurdle of being afraid to dive into front end at all.

Update: With the help of an html validator (https://validator.w3.org/nu/), I discovered an errant space in a carousel element id.  That's it.  So aggravating.  But hey, a new tool learned.


Sunday, February 28, 2016

w8d(-1) - making image thumbnails

I spent a lot of the weekend sucking the marrow out of my Costa Rica experience, so to speak.  I did manage to figure out how to dynamically create a thumbnail of a user-uploaded image.  It was not easy.

Challenges:

  1. the PIL (python image library) that most stack overflow questions referenced is no longer supported.  Pillow, a fork of that library, is still supported, however.
  2. The Pillow tutorial for making a thumbnail referred to opening files from the users file structure, but I wanted to use the Image file object without writing it to disk.
I wrote a step by step guide since others in the class had expressed interest in this.  I'm fairly proud that I was able to get all this to work.  I'd like to, next, get it to run in the background since the user experience is currently held up while the files upload to S3 and get resized.

Guide:


Creating thumbnails from uploaded files (in django/python 2.7)


ultimate goal: accept a file from a user, create a thumbnail from the file, upload both original file and thumbnail to S3, and record link to respective files.  Note: this does not take into account a user uploading an enormous file which would have to be processed in pieces (Django default limit is 2.5 MB)

preparatory stuff:
a. pip install Pillow (http://pillow.readthedocs.org/en/3.1.x/installation.html), a python module for image manipulation.  Any stack overflow answers that refer to PIL (python image library) should work as written, so long as you have installed Pillow. (PIL is no longer maintained, but Pillow is a fork that is still being maintained).  Exceptions may exist

b. in views:
            from django_boto.s3 import upload
            import StringIO
            from PIL import Image
            from django.core.files.uploadedfile import InMemoryUploadedFile

1. I needed to create a form that included a file upload as one of several fields (vs. a form that accepted ONLY a file upload)
corresponding html for the upload file input (name=”pic”).  The accept=”image/*” means that the form will post only when an image file has been selected.  The * means any image format is ok.  You could constrain the format to jpg, tiff, png etc.

           

2.  Once the form is sent via POST request, the file is accessed via request.FILES[‘pic’]

            new_upload=request.FILES[‘pic’]


3. Open the uploaded file as an Image object (from the Pillow library):

            newThumb=Image.open(StringIO.StringIO(new_upload.read())

4. set a variable that contains a size for the thumbnail.  The format is a tuple with height and width (h,w).  To keep the same proportions, but resize for the long side to be 128 px, for example:

5. make the damn thumbnail! Note that calling the .thumbnail method changes the Image object in place.  You cannot set newThumb.thumbnail equal to a variable.  The Image.ANTIALIAS ensures your downsized image still looks reasonably good)

            newThumb.thumbnail(thumb_size, Image.ANTIALIAS)

6. You need a name for the new thumbnail file.  Remember, “new_upload) is an object.  It has already been stripped of it’s file path, but it still has the original upload name (eg. “fancycat.jpg”)
           
            upload_name = new_upload.name.split(.)
            thumb_name=upload_name[0] + “_thumb” + upload_name[1]

result is “fancycat_thumb.jpg”

7. The fun part!  Create a “django file-like object” to hold a temporary version of your thumbnail.  Using this method, you don’t have actually create a file and write it to your file system. (source: http://stackoverflow.com/questions/3723220/how-do-you-convert-a-pil-image-to-a-django-file)

Think of how you do
       with (“file.txt”, wb+) as f:
                   f.readlines()
‘f’ is the ‘file-like object’

To create the file-like object, however, you use:

            thumb_io=StringIO.StringIO()

8. save the thumbnail image object (newThumb) to the file-like object using the Image method .save():
           
            newThumb.save(thumb_io, format=”JPEG”)

9. Associate file properties with the file-like object.  (I think that’s what this step is doing.  I’ll give you a dollar/550 colones if you want to look up the documentation because it’s 11PM and IDGAF)

            thumb_file=InMemoryUploadedFile(thumb_io, None, thumb_name, ‘image/jpeg’, thumb_io.len, None)

10.  Finally, you can upload your simulated file (and your original full-size file).  Remember to catch the url:

            upload_path=upload(new_upload)
            thumb_path=upload(thumb_file)

11.  Finally finally, create a new instance of the model that will store the data.  In my case, the model is called Cat_pictures

            new_pic=Cat_pictures()
     
            (in models.py):

            class Cat_pictures(models.Model):
                        url=models.TextField(blank=True)
                        thumb_path=models.TextField(blank=True)

      the blank=True argument allows users to submit a new sighting without submitting a photo if wanted.
           
            in views.py:

            new_pic.url=upload_path
            new_pic.thumb_path=thumb_path

12. Don’t forget to save the new instance.

            new_pic.save()

w7d5 - google maps, and javascript!

My location page actually works now! It can:


  1. allow a user to choose automatic detection of location from their current device or to enter street address
  2. find a user's location using the html5 location methods
  3. display that location on an embedded google map
  4. accept a standard google text search (i.e. anything you'd normally use on google maps, like a zip code, or a street address, or a city name)
  5. show the street address location on a map
  6. allow users to drag the icon to a desired location
  7. capture the latitude and longitude of the marker and build a redirect url using javascript
  8. capture data passed in to the location page via url api and include that in the redirect url after the location has been captured.

So, yes, most of the js was actually written by google in their documentation.  I had to adapt several examples, though, to do my more complex implementation (to give users a choice of methods based on mouse events).

That was Friday.  The weekend gets its own post this time.  Cheers!

on page load:

on selecting Address:
on entering an address:

Stuff appears and disappears like magic!

Now I just have to make it look less shitty...

Thursday, February 25, 2016

w7d3&4 More django project and the eternal hell of forms

I missed a blog entry!  I did so since our illustrious TA is leaving a week early and our going-away-party lasted into the wee hours of Wednesday night/Thursday morning.  I'm proud to report that I did my part to uphold GoCode's honor at the fussball table.

In other news, catfinder progress continues at a snail's pace.  I am, however, learning quite a lot.

After my glorious discovery of custom template tags (all the way back on Tuesday) I was faced with a daunting prospect of a lot of cumbersome in-template dictionary lookups and for-loops with for-loops within for-loops.  So I went back to the drawing board and figured out a way of creating a custom object in the views.py that holds all the relevant data from the 4 lookup tables neccessary for the "local cats page" (each cat with multiple sightings, each sighting with multiple pictures and a single (human) user).  Bonus: since it's a single object it can be serialized and returned as a JSON object should (when!) FriendlyCatFinder.com becomes an important information source.

I can just imagine the code schools of future with projects based around Yelp clones that show bars in a certain area, modified by number of accessible cats within walking distance.

Wednesday morning was a tutorial in Amazon S3 for image hosting, and getting django to upload the image and save the resulting path. Woo.

Today was spent in integrating the file upload and modifying it work with my forms.  And getting my "new sighting" form to store everything properly.  I don't know why it took so long to do, even with ignoring any kind of styling at all.

My plan, such as it is, is to get all the pieces working, and to make it look good afterwards.  Otherwise, I know I'll get stuck in an endless loop of tweaking css to make the corners of my form boxes just the perfect degree of roundness.

Done: front page, login, logout, new user, admin functionality, local cats, new sighting, new cat, image uploads

To do:  get google maps api working (BIG), user profiles, user editing existing info

Tuesday, February 23, 2016

w7d2 - Progress, step by bloody step by custom django template tag

It's a weird feeling to realize that I'm still taking in vast amounts of information, while feeling like I'm spinning my wheels, so to speak.  I'm at the point where I need to know so much that each little thing I add to my stock of knowledge disappears almost without a trace.

The lecture today was on making our own APIs and returning information from an external query as json or xml.  The exciting part was learning how to implement this within my own project.

For example: I have a "location" page that asks users to determine the location they wish to search.  For some reason, cats are not yet stored as destinations in google maps, so I need to query the catfinder database to find all the cats in a given area.  To do this I needed:

  • to work with decimal data types since geolocation data is best handled as decimal(10,6) data type (i.e. up to 10 total characters, with no more than 6 places to the right of the decimal.  Not ridiculously difficult, but I spent some time playing with mathematical operations to see that I wouldn't be fouling anything.
  • work out how to query sql for distance from a point.  The general formula I found is 

SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance FROM markers HAVING distance < 25 ORDER BY distance LIMIT 0 , 20;
although that's for a distance of a mile from a point, taking into account the curve of the Earth's surface.  My distances need to be pretty close (since most cats don't wander far), so I'm going to ignore curvature.

My solution was to define a search "radius" as 0.010000 decimal degrees.  This translates to about 1 km (courtesy of http://www.csgnetwork.com/gpsdistcalc.html) and to return all cat sightings that are within 0.010000 degrees of the search point.


  • (back to bullets)
  • Next problem: My data for "local cats" page (showing proximal cats) looks like this 
  • to populate that page, I need to organize by "cat".  One cat can have many sightings (cat_id is a foreign key in sightings)
  • Each sighting has several photos (in a separate cat_photos table with a foreign key from each photo to a sighting)
  • Each sighting has a user who submitted the info, and who may wish to edit old submissions
I worked out the query structure in for use with django (a bunch of filter requests) and returned the data as dictionary. 

Dictionaries are awesome!  So easy to use in python!

Not in django templates, however.  Or at least not as we were taught django templates.  You can pass a dictionary into a template as part of context, but you can't readily access the values using standard dictionary __get_item__. (see here for relevant Stack Overflow discussion).  

I'll spare you the misery I experienced as I tried everything I could think of, before getting the right search results to figure out what was going on.  

Solution (I think) is to write a custom tag.  I spent all evening looking reading and rereading sections, but I got one that works for dictionary lookup!  It's too late now to fully test it, and I don't have my locations page working like I want yet...but it works in basic form.  
tag:

@register.filter(name='lookup',is_safe=True)
def lookup(dict,key):
    return dict[key]

implementation:

{{ <dict>| lookup:<key> }}


  • Also (this deserves it's own bullet): I was able to assign a variable using the templating language {% with info| lookup:"id" as x %}
                    <p>cat name: {{ x.name }}</p>
                    {% endwith %}
  • woo hoo!
So, now that I look back on it, that's a huge amount of ground covered in one day.  I don't 100% understand everything (decorators are still beyond my own efforts), but I was able to read and implement the documentation.  I worked with a new data type (decimal).  I figured out my own (hacktastic!) geo-distance formula.  I avoided a tantrum at the 100th error message.

So.  Win.