fíam

(rhymes with liam)

  • Filtrus

    May 6, 2009 at 01:25:49 CEST

    Finalmente, hoy he podido encontrar tiempo para empezar a trabajar en el proyecto sobre el que escribí la semana pasada. Todavía pasarán unos días antes de que pueda publicar algo concreto, pero los progresos son importantes. He reescrito más o menos un 50% del código del backend web, cambiando el nombre original y portándolo a Django 1.1. Una vez termine esta fase, subiré toda esa parte a github y me pondré a portar el engine de recomendaciones a PostgreSQL.

    ¿Tiempo estimado? Probablemente el backend esté terminado para el viernes, mientras que al engine de recomendaciones probablemente me lleve uno o dos días más. El frontend por ahora no lo voy a modificar, aunque será en lo primero que me ponga a trabajar una vez la página esté funcionando.

    Lo que sí os puedo ya adelantar es el nombre del futuro sitio: filtrus.

    /goes back to coding

  • Creemos una alternativa

    May 6, 2009 at 01:18:04 CEST

    Son muchos los problemas de menéame y, probablemente, muchos seáis conscientes de ellos, así que me voy a saltar esa parte y me voy a ir directo a lo interesante.

    Hace unos meses, simplemente por diversión, me dediqué a programar un sitio al estilo de menéame a digg, intentando llevar esos mismos conceptos al siguiente nivel. Terminé cerrándolo porque, evidentemente, nadie lo usaba (es difícil darse a conocer cuando no tienes intención de gastarte dinero en publicidad) y mi único motivo para mantenerlo era divertirme y experimentar, cosa complicada cuando tu masa de usuarios ronda la centena.

    No voy a entrar en demasiados detalles, ya que no quiero que está entrada termine siendo demasiado pesada, pero sí haré una overview de las características del sitio que programé:

    • Sistema de recomendaciones
    • Cada usuario tiene una portada personalizada, basada en sus preferencias calculadas a partir de sus votos (llamemos a esta sección unfiltered)
    • Existe también una sección al estilo de la portada de menéame, donde se muestran las noticias que han superado un número determinado de puntos (filtered)
    • El sistema de promoción se basa completamente en las recomendaciones: cuanto menos probable sea que me guste una noticia, más peso tendrá un voto positivo emitido por mí (y menos peso un voto negativo). De esta forma se consigue una variedad increíble de contenidos, pues se evita completamente el "efecto enjambre" (mafia o bury brigades, como dicen en otros sitios).
    • Las noticias se organizan tanto por etiquetas como de forma jerárquica atendiendo a su localización (en las que sea aplicable). De esta forma, puedes por ejemplo consultar sólo la portada de las noticias que suceden en tu comunidad autónoma. Esto se aplica también al algoritmo de promoción, de forma que, por ejemplo, una noticia de Madrid primero tendrá que promocionar en Madrid, luego en España, a continuación en Europa y, finalmente, podrá pasar a la portada global (si consigue votos suficientes, claro).
    • El sistema soporta completamente tanto inglés como castellano (tanto en el interfaz como en los sistema de recomendación), pudiendo escoger el usuario el idioma del contenido al que quiere acceder.

    No obstante, y a pesar de que este sitio a día de hoy está cerrado, gran parte de su código está liberado y puede ser accedido a través de mis repositorios de github. Hay partes que aún no las he liberado debido a que mi trabajo actual no me ha dejado tiempo para terminar de limpiar todo el código, pero mi intención es que sea todo libre.

    Y, bien, ¿qué busco con esta entrada? Ofrecer a la comunidad el sistema que yo programé para que si, hay alguien interesado en mejorar las cosas que faltan, podamos entre todos crear un nuevo sitio que nos permita intercambiar contenidos sin que haya una veintena de personas que dicten lo que sale a portada y lo que no. Si hay suficiente gente interesada (tanto en ayudarme a pulir los detalles del código que faltan como a colaborar enviando y votando noticias), yo me comprometo a liberar todo este código y crear una página. Además, cualquiera que desee crearse un clon será también bienvenido.

    En cuanto a las características técnicas, el código del backend es Python y utiliza Django, mientras que todo el sistema de recomendaciones está escrito en C usando Glib (una de las librerías base de Gnome). El frontend es simplemente XHTML y se utiliza un poco de jQuery. Los cambios más importantes que habría que hacer son los siguientes:

    • Portar el backend a Django 1.0 (esto no debería ser mucho trabajo)
    • Portar el javascript a jQuery 1.3
    • Actualmente, el sistema de recomendaciones acceder a la base de datos utilizando la API nativa de MySQL. Sería deseable, por su mejor soporte de GIS, portar todo esto a PostgreSQL, aunque el sitio funciona perfectamente sobre MySQL.
    • No vendría mal mejorar un poco el diseño gráfico, puesto que el actual está hecho por mí y yo soy un tanto manco para esas cosas

    Si hay alguien interesado en colaborar con este proyecto, que deje un comentario y me envíe un correo (está a la derecha) y yo me pondré en contacto con él con la mayor brevedad posible.

  • Adding a simple WYSIWYM Markdown editor to your models

    March 9, 2009 at 10:01:54 CET

    I'm a Markdown fan. Period. Since I first discovered it some years ago, I've been using it for storing all my "output-to-HTML" texts.

    Blango, the blog engine I wrote for this site, uses Markdown for post contents. As I've said, I really like Markdown, but typing (and sometimes escaping) all those *[]() gets boring from time to time. I've been looking for something like a WYSIWYG editor for some time, but none of the available choices seemed good enough for me. However, I recently stumbled upon WMD.

    So, what's WMD and why should I care?

    In the author's own words:

    So WMD is something new: a Wysiwym Markdown editor.

    Wysiwym stands for What You See Is What You Mean. WMD produces clean semantic HTML, leaving presentation details like fonts and colors up to style sheets. But you're not left in the dark about cosmetics; as you type, WMD's live preview shows you exactly what your text will look like after the current styles have been applied.

    Markdown is a simple markup language that's as easy to read and write as plain-text email. WMD uses Markdown as an input format, converting it into HTML behind the scenes. You can also mix raw HTML with Markdown, so the full expressiveness of HTML is always there if you need it.

    Plus, WMD is comprised of Javascript and image files, doesn't need server support, doesn't depend on any Javascript Framework and it's just a small rectangle over the textarea. Id est, it doesn't get in my way.

    Downloading the required files

    WMD was a hosted service until recently and is currently provided as a set of Javascript obfuscated files. The author is working on a new release, but this is all we got for now. Anyway, good enough.

    You can get the last version (1.0.1 as the time of this writing) from project's downloads page. Just open the zipfile and copy the wmd directory to your MEDIA_ROOT.

    If you're storing your posts as Markdown and doing the conversion to HTML (as you should be doing), you need to take one extra step. Open wmd/wmd.js and change line 4 to read:

    Attacklab.wmd_defaults={version:1,output:"Markdown",lineLength:40,delayLoad:false};
    

    WMD will preprocess the Markdown and send HTML to the server otherwise.

    Adding WMD support to your models

    By default, WMD will attach an editor to every textarea. So, all we need to do is tell the admin interface to include wmd/wmd.js when adding or editing our models. This can be done by using the inner class Media in your ModelAdmin subclass. For example:

    class EntryAdmin(admin.ModelAdmin):
        class Media:
            js = ( 'js/wmd/wmd.js', )
    

    And you're ready to go. Now your models will have a small rectangle featuring the WMD over every textarea. WMD

  • Keeping your Localizable.strings in UTF-8

    March 9, 2009 at 09:25:30 CET

    Since the iPhone SDK 2.2, Localizable.strings files are required to be in UTF-16 encoding or they will be ignored while running in the device otherwise. This seemed very inconvenient for us, for multiple reasons:

    • git and svn don't play well with UTF-16. The files were treated as binaries and we couldn't do merges on them
    • Our SCM and bug tracking tools didn't play well with UTF-16. Everytime we referenced a patch against UTF-16 files, it showed up as garbage while reviewing the changes on the web front end
    • Our translations teams prefer UTF-8

    We could have properly configured git and svn for every developer, bugged our external SCM and bug tracking solution vendor to add support for UTF-16 and told our translation teams to STFU. However, this seemed like a lot of work for just a bunch of text files. Instead, I chose keeping them in UTF-8 and translating them to UTF-16 while building the project.

    Creating the necessary files

    First, you need to rename your Locable.strings to Localizable.strings.in. The easier way of doing this is right-clicking on Localizable.strings from your Xcode project and selecting the Rename option in the popup menu. Then, for every Localizable.strings.in file in your project, touch (as in typing touch filename from the terminal) a new Localizable.strings and add all of them to your project. You don't need to add this new Localizable.string files to your SCM, but you need to add them to your Xcode project, it won't copy them to the final bundle otherwise. Keep in mind that these files don't need to be present for building your project, since we'are going to generate them in the first build phase. After this step, your resources should look like this:

    Localizable.strings

    Adding a build phase

    Now we need to tell Xcode to translate our files from UTF-8 to UTF-16. We're using a "Run Script" build phase and we need to create it before any other ones:

    Build Phases

    Finally, select "Get Info" to get to the phase properties and paste the following as the script contents:

    # Adjust ${PROJECT_DIR}"/Languages to suit your needs
    for LocalizableStringsIn in $(find "${PROJECT_DIR}"/Languages -name "Localizable.strings.in" -print)
    do
        LocalizableStrings="${LocalizableStringsIn/Localizable.strings.in/Localizable.strings}"
        iconv -f UTF-8 -t UTF-16BE < "${LocalizableStringsIn}" > "${LocalizableStrings}"
    done
    

    A final note

    We extract our translations and merge them a custom script, but if you're using genstrings, you should keep in mind that it generates UTF-16 files, so you'll need to pipe its output trough iconv to do the opposite conversion when generating your Localizable.strings.in files.

  • My take on UIDatePickerController

    Feb. 23, 2009 at 13:59:59 CET

    I've recently seen some UIDatePickerController implementations around, so I think I'm going to share mine. What are the differences with others?

    • Efficient: The date formatter is just allocated once and the table view never reloads the data.
    • Configurable: The date picker and the date formatter are exposed via properties, so you can configure them however you want to.
    • Flexible: No hardcoded frames. Positions are determined at runtime, so it will work regardless of the view height (e.g. having a tab bar will change the view height).
    • No strings: This code uses no strings, which means you don't need to translate them to every language you support.

    This is the code. Alternatively, you can also download the files: UIDatePickerController.h UIDatePickerController.m

    /* UIDatePickerViewController.h */
    #import <UIKit/UIKit.h>
    
    @class UIDatePickerController;
    
    @protocol UIDatePickerControllerDelegate
    
    @required
    - (void)datePickerControllerCancelled:(UIDatePickerController *)picker;
    - (void)datePickerControllerSaved:(UIDatePickerController *)picker withDate:(NSDate *)theDate;
    
    @end
    
    @interface UIDatePickerController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
        UIDatePicker *_picker;
        UITableViewCell *_cell;
        UITableView *_tableView;
        NSDateFormatter *_formatter;
        id <NSObject, UIDatePickerControllerDelegate> _delegate;
    }
    
    @property(nonatomic, readonly) UIDatePicker *picker;
    @property(nonatomic, readonly) NSDateFormatter *formatter;
    @property(nonatomic, retain) NSDate *date;
    @property(nonatomic, assign) id <NSObject, UIDatePickerControllerDelegate> delegate;
    
    @end
    
    /* UIDatePickerViewController.m */
    #import <QuartzCore/QuartzCore.h>
    
    #import "UIDatePickerController.h"
    
    
    @implementation UIDatePickerController
    
    @synthesize picker = _picker;
    @synthesize formatter = _formatter;
    @synthesize delegate = _delegate;
    
    - (id)init {
        if (self = [super init]) {
            _picker = [[UIDatePicker alloc] initWithFrame:CGRectZero];
            [_picker addTarget:self action:@selector(dateDidChange:) forControlEvents:UIControlEventValueChanged];
            _tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
            _formatter = [NSDateFormatter new];
    
            _tableView.delegate = self;
            _tableView.dataSource = self;
        }
    
        return self;
    }
    
    #pragma mark properties
    
    - (NSDate *)date {
        return _picker.date;
    }
    
    - (void)setDate:(NSDate *)theDate {
        _picker.date = theDate;
    }
    
    - (void)loadView {
        [super loadView];
    
        self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
        [self.view addSubview:_picker];
        [self.view addSubview:_tableView];
    
        UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
                                                                                      target:self action:@selector(cancel)];
        self.navigationItem.leftBarButtonItem = cancelButton;
        [cancelButton release];
        UIBarButtonItem *saveButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave
                                                                                    target:self action:@selector(save)];
        self.navigationItem.rightBarButtonItem = saveButton;
        [saveButton release];
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
    
        CGFloat pickerYOrigin = CGRectGetMaxY(self.view.layer.visibleRect) - CGRectGetHeight(_picker.frame);
        _picker.frame = CGRectMake(0, pickerYOrigin, CGRectGetWidth(_picker.frame), CGRectGetHeight(_picker.frame));
    
        CGFloat viewYOrigin = CGRectGetMinX(self.view.bounds);
        CGFloat tableHeight = 65;
        CGFloat tableYOrigin = viewYOrigin + (pickerYOrigin - viewYOrigin - tableHeight) / 2;
        _tableView.frame = CGRectMake(0, tableYOrigin, CGRectGetWidth(self.view.frame), tableHeight);
    }
    
    #pragma mark Table view methods
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
        return 1;
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return 1;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
        if (_cell == nil) {
            _cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:nil] autorelease];
            _cell.font = [UIFont systemFontOfSize:17.0];
            _cell.textColor = [UIColor colorWithRed:62/255.0 green:78/255.0 blue:111/255.0 alpha:1.0];
            _cell.selectionStyle = UITableViewCellSelectionStyleNone;
        }
    
        _cell.text = [_formatter stringFromDate:_picker.date];
    
        return _cell;
    }
    
    - (void)dealloc {
        [_picker release];
        [_tableView release];
        [_formatter release];
        [super dealloc];
    }
    
    - (void)save {
        [_delegate datePickerControllerSaved:self withDate:_picker.date];
    }
    
    - (void)cancel {
        [_delegate datePickerControllerCancelled:self];
    }
    
    - (void)dateDidChange:(UIDatePicker *)picker {
        _cell.text = [_formatter stringFromDate:picker.date];
    }
    
    @end