Unable to save django-toolbox ListField to databas

2019-08-17 22:43发布

问题:

I am writing a django-nonrel based app and using admin view functionality provided by Django.

I want a Many-to-many relationship between two models and for this, I am using ListField found inside djangotoolbox.fields

To provide a custom form field, I have overridden ListField with another class ModelListField as described in this question which overrides formfield function to return a MultipleChoiceField form widget.

The view part works fine but I am unable to save the model using sqlite3 backend.

Assume, two models (Standard Many-to-many relationship between note and tags)

from django.db import models

class Tag(models.Model):
    name = models.CharField(max_length=255)

class Note(models.Model):
    tags = ModelListField(db.ForeignKey(Tag))

With these change, add note page appears correctly in admin interface but when I try to save the note, I get the following exception:

InterfaceError, Error binding parameter 0 - probably unsupported type.  
inside django\db\backends\sqlite3\base.py in execute, line 234

The line 234 reads as follows:

class SQLiteCursorWrapper(Database.Cursor):
    """
    Django uses "format" style placeholders, but pysqlite2 uses "qmark" style.
    This fixes it -- but note that if you want to use a literal "%s" in a query,
    you'll need to use "%%s".
    """
    def execute(self, query, params=()):
        query = self.convert_query(query)
        try:
234 ---->           return Database.Cursor.execute(self, query, params)
        except Database.IntegrityError, e:
            raise utils.IntegrityError, utils.IntegrityError(*tuple(e)), sys.exc_info()[2]
        except Database.DatabaseError, e:
            raise utils.DatabaseError, utils.DatabaseError(*tuple(e)), sys.exc_info()[2]

And query & params passed to call are:

query: 'INSERT INTO "note" ("tags") VALUES (?)'  
params: [1, 2, 3]
where 1, 2, 3 are pk of three tag fields in database.

When creating model, I specify the COLUMN TYPE of tags as "ListField" i.e. CREATE table SQL query is:

CREATE TABLE "notes" (
    "id" integer NOT NULL PRIMARY KEY,
    "tags" ListField NOT NULL
)

Database query execute call above barfs when it sees the list [1,2,3]. Instead of list, I tried to pass the list as smart_unicode(list) or just a string "1 2 3" but then ListField throws error while validating (in get_db_prep_value) because it expects list.

I am unable to understand whether it is correct to pass the list object to Database.Cursor.execute call above or something is missing as ListField is correctly expecting list but when writing to database, someone should have converted this list into string etc..?

There's no good example of how to use ListField :-( Thanks for reading a long & boring description..

回答1:

I figured out it is the responsibility of get_db_prep_save to prepare data in correct format for saving to database. So, in ModelListField, I override get_db_prep_save and convert it to string to fix the "Unsupported type" error.

def get_db_prep_save(self, value, connection):
    retval = super(ModelListField, self).get_db_prep_save(value)
    return unicode(retval)

I also faced an issue with Select Widget not showing tags with pk > 10 as selected. Inside django-non 1.3, I had to add eval in below function:

class Select(Widget):
    def render_options(self, choices, selected_choices):
        **if(type(selected_choices) != list):
            selected_choices = eval(selected_choices)**

This was done because render_option was called with list as string i.e. "[1,2,3]" instead of just [1,2,3], so string was converted back to list using eval. Not sure if these two are related or latter issue is bug with django-nonrel.

As dragonx said, maybe it's not a good idea to test with sqlite3 and django-nonrel. I will change my backend soon.