I have a script, migrating Image to a custom type, for my Plone instance. Its partial code sample looks like this:
class ImagesToPhotosMigrator(InplaceATItemMigrator):
src_portal_type = 'Image'
src_meta_type = 'ATBlob'
dst_portal_type = 'Photo'
dst_meta_type = 'Photo'
def last_migrate_reindex(self):
self.new.reindexObject(idxs=['object_provides', 'portal_type',
'Type', 'UID'])
fields_map = {
}
def getImagesToPhotosMigrationWalker(self):
return getMigrationWalker(self, migrator=ImagesToPhotosMigrator)
def migrateImages(self):
walker = getImagesToPhotosMigrationWalker(self)
walker.go()
return walker.getOutput()
The script works, but I want the migration only happens in a specific folder, say /my-folder, what to add in the script?
You should have specified that you refer to Products.contentmigration, cause it's not implicit in the words "content migration" (lately there are more kind of migrations than lines of code). Anyway, here your solution (CustomQueryWalker is the key):
from Products.contentmigration.walker import CustomQueryWalker
def getImagesToPhotosMigrationWalker(self, query):
return CustomQueryWalker(self, ImagesToPhotosMigrator, query)
def migrateImages(self):
walker = getImagesToPhotosMigrationWalker(self, {'path' : '/youfolder'})
walker.go()
return walker.getOutput()
Note that the query parameter is a catalog query, therefore you can specify the path, the portal_type or any index in your catalog.
Related
I am trying to build an accounting database using flask as the front end. The main page is the ledger, with nine columns "date" "description" "debit" "credit" "amount" "account" "reference" "journal" and "year", I need to be able to query each and some times two at once, there are over 8000 entries, and growing. My code so far displays all the rows, 200 at a time with pagination, I have read "pep 8" which talks about readable code, I have read this multiple parameters and this multiple parameters and like the idea of using
request.args.get
But I need to display all the rows until I query, I have also looked at this nested ifs and I thought perhaps I could use a function for each query and "If" out side of the view function and then call each in the view function, but I am not sure how to. Or I could have a view function for each query. But I am not sure how that would work, here is my code so far,
#bp.route('/books', methods=['GET', 'POST'])
#bp.route('/books/<int:page_num>', methods=['GET', 'POST'])
#bp.route('/books/<int:page_num>/<int:id>', methods=['GET', 'POST'])
#bp.route('/books/<int:page_num>/<int:id>/<ref>', methods=['GET', 'POST'])
#login_required
def books(page_num, id=None, ref=None):
if ref is not None:
books = Book.query.order_by(Book.date.desc()).filter(Book.REF==ref).paginate(per_page=100, page=page_num, error_out=True)
else:
books = Book.query.order_by(Book.date.desc()).paginate(per_page=100, page=page_num, error_out=True)
if id is not None:
obj = Book.query.get(id) or Book()
form = AddBookForm(request.form, obj=obj)
if form.validate_on_submit():
form.populate_obj(obj)
db.session.add(obj)
db.session.commit()
return redirect(url_for('books.books'))
else:
form = AddBookForm()
if form.validate_on_submit():
obj = Book(id=form.id.data, date=form.date.data, description=form.description.data, debit=form.debit.data,\
credit=form.credit.data, montant=form.montant.data, AUX=form.AUX.data, TP=form.TP.data,\
REF=form.REF.data, JN=form.JN.data, PID=form.PID.data, CT=form.CT.data)
db.session.add(obj)
db.session.commit()
return redirect(url_for('books.books', page_num=1))
return render_template('books/books.html', title='Books', books=books, form=form)
With this code there are no error messages, this is a question asking for advice on how to keep my code as readable and as simple as possible and be able to query nine columns of the database whilst displaying all the rows queried and all the rows when no query is activated
All help is greatly appreciated. Paul
I am running this on debian 10 with python 3.7
Edit: I am used to working with Libre Office Base
My question is How do I search one or two columns at a time in My database where I have nine columns out of twelve that I want to be able to search, I want to be able to search one or more at a time, example: column "reference" labels a document reference like "A32", and "account" by a the name of the supplier "FILCUI", possibly both at the same time. I have carried out more research and found that most people advocate a "fulltext" search engine such as "Elastic or Whoosh", But in my case I feel if I search "A32" ( a document number) I will get anything in the model of 12 columns with A 1 2. I have looked at Flask Tutorial 101 search Whoosh all very good tutorials, by excellent people, I thought about trying to use SQLAlchemy as a way, but in the first "Flask Tutorial" he says
but given the fact that SQLAlchemy does not support this functionality,
I thought that this SQLAlchemy-Intergrations will not work either.
So therefor is there a way to "search" "query" "filter" multiple different columns of a model with possibly a form for each search without ending up with a "sack of knots" like code impossible to read or test? I would like to stick to SQLAlchemy if possible
I need just a little pointer in the right direction or a simple personal opinion that I can test.
Warm regards.
EDIT:
I have not answered my question but I have advanced, I can query one row at a time and display all the results on the one page, with out a single "if" statement, i think my code is clear and readable (?) I divided each query into its own view function returning to the same main page, each function has its own submitt button. This has enabled me to render the same page. here is my routes code.
#bp.route('/search_aux', methods=['GET', 'POST'])
#login_required
def search_aux():
page_num = request.args.get('page_num', default = 1, type = int)
books = Book.query.order_by(Book.date.desc()).paginate(per_page=100, page=page_num, error_out=True)
add_form = AddBookForm()
aux_form = SearchAuxForm()
date_form = SearchDateForm()
debit_form = SearchDebitForm()
credit_form = SearchCreditForm()
montant_form = SearchMontantForm()
jn_form = SearchJNForm()
pid_form = SearchPIDForm()
ref_form = SearchREForm()
tp_form = SearchTPForm()
ct_form = SearchCTForm()
des_form = SearchDescriptionForm()
if request.method == 'POST':
aux = aux_form.selectaux.data
books = Book.query.order_by(Book.date.desc()).filter(Book.AUX == str(aux)).paginate(per_page=100, page=page_num, error_out=True)
return render_template('books/books.html', books=books, add_form=add_form, aux_form=aux_form, date_form=date_form, debit_form=debit_form,
credit_form=credit_form, montant_form=montant_form, jn_form=jn_form, pid_form=pid_form, ref_form=ref_form,
tp_form=tp_form, ct_form=ct_form, des_form=des_form)
There is a simple form for each query, it works a treat for each single query. Here is the form and html code:
class SearchAuxForm(FlaskForm):
selectaux = QuerySelectField('Aux', query_factory=AUX, get_label='id')
submitaux = SubmitField('submit')
def AUX():
return Auxilliere.query
html:
<div class="AUX">
<form action="{{ url_for('books.search_aux') }}" method="post">
{{ aux_form.selectaux(class="input") }}{{ aux_form.submitaux(class="submit") }}
</form>
</div>
I tried to do this as a single function with one submit button, but it ended in disaster. I have not submitted this as an answer, Because it does not do all I asked but it is a start.
FINAL EDIT:
I would like to thank the person(s) who reopened this question, allowing mr Lucas Scott to provide a fascinating and informative answer to help me and others.
There are many ways to achieve your desired result of being able to query/filter multiple columns in a table. I will give you an example of how I would approach creating an endpoint that will allow you to filter on one column, or multiple columns.
Here is our basic Books model and the /books endpoint as a stub
import flask
from flask_sqlalchemy import SQLAlchemy
app = flask.Flask(__name__)
db = SQLAlchemy(app) # uses in memory sqlite3 db by default
class Books(db.Model):
__tablename__ = "book"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(255), nullable=False)
author = db.Column(db.String(255), nullable=False)
supplier = db.Column(db.String(255))
published = db.Column(db.Date, nullable=False)
db.create_all()
#app.routes("/books", methods=["GET"])
def all_books():
pass
The first step is to decide on a method of querying a collection by using url parameters. I will use fact that multiple instances of the same key in a query parameter are given as lists to allow us to filter on multiple columns.
For example /books?filter=id&filter=author will turn into {"filter": ["id", "author"]}.
For our querying syntax we will use comma separated values for the filter value.
example:
books?filter=author,eq,jim&suplier,eq,self published
Which turns into {"filter": ["author,eq,jim", "supplier,eq,self published"]}. Notice the space in self published. flask will handle the url-encoding for us and give back a string with a space instead of %20.
Let's clean this up a bit by adding a Filter class to represent our filter query parameter.
class QueryValidationError(Exception):
""" We can handle specific exceptions and
return a http response with flask """
pass
class Filter:
supported_operators = ("eq", "ne", "lt", "gt", "le", "ge")
def __init__(self, column, operator, value):
self.column = column
self.operator = operator
self.value = value
self.validate()
def validate(self):
if operator not in self.supported_operators:
# We will deal with catching this later
raise QueryValidationError(
f"operator `{operator}` is not one of supported "
f"operators `{self.supported_operators}`"
)
Now we will create a function for processing our list of filters into a list of Filter objects.
def create_filters(filters):
filters_processed = []
if filters is None:
# No filters given
return filters_processed
elif isinstance(filters, str):
# if only one filter given
filter_split = filters.split(",")
filters_processed.append(
Filter(*filter_split)
)
elif isinstance(filters, list):
# if more than one filter given
try:
filters_processed = [Filter(*_filter.split(",")) for _filter in filters]
except Exception:
raise QueryValidationError("Filter query invalid")
else:
# Programer error
raise TypeError(
f"filters expected to be `str` or list "
f"but was of type `{type(filters)}`"
)
return filters_processed
and now we can add our helper functions to our endpoint.
#app.route("/books", methods=["GET"])
def all_books():
args = flask.request.args
filters = create_filters(args.get("filter"))
SQLAlchemy allows us to do filtering by using operator overloading. That is using filter(Book.author == "some value"). The == here does not trigger the default == behaviour. Instead the creator of SQLAlchemy has overloaded this operator and instead it creates the SQL query that checks for equality and adds it to the
query. We can leverage this behaviour by using the Pythons operator module. For example:
import operator
from models import Book
authors = Book.query.filter(operator.eq(Book.author, "some author")).all()
This does not seem helpful by it's self, but gets us a step closer to creating a generic and dynamic filtering mechanism. The next important step to making this more dynamic is with the built-in getattr which allows us to look up attributes on a given object using strings. Example:
class Anything:
def say_hi(self):
print("hello")
# use getattr to say hello
getattr(Anything, "say_hi") # returns the function `say_hi`
getattr(Anything, "say_hi")() # calls the function `say_hi`
We can now tie this all together by creating a generic filtering function:
def filter_query(filters, query, model):
for _filter in filters:
# get our operator
op = getattr(operator, _filter.operator)
# get the column to filter on
column = getattr(model, _filter.column)
# value to filter for
value = _filter.value
# build up a query by adding multiple filters
query = query.filter(op(column, value))
return query
We can filter any model with our implementation, and not just by one column.
#app.route("/books", methods=["GET"])
def all_books():
args = flask.request.args
filters = create_filters(args.get("filter"))
query = Books.query
query = filter_query(filters, query, Books)
result = []
for book in query.all():
result.append(dict(
id=book.id,
title=book.title,
author=book.author,
supplier=book.supplier,
published=str(book.published)
))
return flask.jsonify(result), 200
Here is everything all together, and including the error handling of validation errors
import flask
import json
import operator
from flask_sqlalchemy import SQLAlchemy
app = flask.Flask(__name__)
db = SQLAlchemy(app) # uses in memory sqlite3 db by default
class Books(db.Model):
__tablename__ = "book"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(255), nullable=False)
author = db.Column(db.String(255), nullable=False)
supplier = db.Column(db.String(255))
published = db.Column(db.Date, nullable=False)
db.create_all()
class QueryValidationError(Exception):
pass
class Filter:
supported_operators = ("eq", "ne", "lt", "gt", "le", "ge")
def __init__(self, column, operator, value):
self.column = column
self.operator = operator
self.value = value
self.validate()
def validate(self):
if self.operator not in self.supported_operators:
raise QueryValidationError(
f"operator `{self.operator}` is not one of supported "
f"operators `{self.supported_operators}`"
)
def create_filters(filters):
filters_processed = []
if filters is None:
# No filters given
return filters_processed
elif isinstance(filters, str):
# if only one filter given
filter_split = filters.split(",")
filters_processed.append(
Filter(*filter_split)
)
elif isinstance(filters, list):
# if more than one filter given
try:
filters_processed = [Filter(*_filter.split(",")) for _filter in filters]
except Exception:
raise QueryValidationError("Filter query invalid")
else:
# Programer error
raise TypeError(
f"filters expected to be `str` or list "
f"but was of type `{type(filters)}`"
)
return filters_processed
def filter_query(filters, query, model):
for _filter in filters:
# get our operator
op = getattr(operator, _filter.operator)
# get the column to filter on
column = getattr(model, _filter.column)
# value to filter for
value = _filter.value
# build up a query by adding multiple filters
query = query.filter(op(column, value))
return query
#app.errorhandler(QueryValidationError)
def handle_query_validation_error(err):
return flask.jsonify(dict(
errors=[dict(
title="Invalid filer",
details=err.msg,
status="400")
]
)), 400
#app.route("/books", methods=["GET"])
def all_books():
args = flask.request.args
filters = create_filters(args.get("filter"))
query = Books.query
query = filter_query(filters, query, Books)
result = []
for book in query.all():
result.append(dict(
id=book.id,
title=book.title,
author=book.author,
supplier=book.supplier,
published=str(book.published)
))
return flask.jsonify(result), 200
I hope this answers your question, or gives you some ideas on how to tackle your problem.
I would also recommend looking at serialising and marshalling tools like marshmallow-sqlalchemy which will help you simplify turning models into json and back again. It is also helpful for nested object serialisation which can be a pain if you are returning relationships.
Using Web2Py RESTFUL services, I'd like the following html to render in the browser after navigating to this link (http://127.0.0.1:8000/app/default/api/example_data.html_table/?limit=1):
<div><table><thead><tr><th>example_data.id</th><th>example_data.Firstname</th><th>example_data.Lastname</th><th>example_data.Age</th></tr></thead><tbody><tr class="w2p_odd odd"><td>1</td><td>SUUUUPPPEEEERRRR LONGGGGG FIRSTTTT NAMEEEE</td><td>Smith</td><td>1</td></tr></tbody></table></div>
However, I currently receive the following:
<div><table><thead><tr><th>example_data.id</th><th>example_data.Firstname</th><th>example_data.Lastname</th><th>example_data.Age</th></tr></thead><tbody><tr class="w2p_odd odd"><td>1</td><td>SUUUUPPPEEEER...</td><td>Smith</td><td>1</td></tr></tbody></table></div>
The difference is that Web2Py is shortening "SUUUUPPPEEEERRRR LONGGGGG FIRSTTTT NAMEEEE" to "SUUUUPPPEEEER..." but I need the entire text
My View called generic.html_table that generates this is the following:
{{=BEAUTIFY(response._vars[next(iter(response._vars))])}}
Controller
#request.restful()
def api():
response.view = 'generic.' + request.extension
def GET(*args,**vars):
patterns = 'auto'
parser = db.parse_as_rest(patterns,args,vars)
return dict(content=parser.response)
def POST(table_name,**vars):
if 'id' in vars.keys():
return db[table_name].update_or_insert(db[table_name]._id == vars['id'],**vars)
else:
return db[table_name].validate_and_insert(**vars)
def PUT(table_name,**vars):
record_id = vars['id']
return db(db[table_name]._id==record_id).update(**vars)
def DELETE(table_name,record_id):
return db(db[table_name]._id==record_id).delete()
return dict(GET=GET, POST=POST, PUT=PUT, DELETE=DELETE)
MODEL
db.define_table('example_data', Field('Firstname', 'string'),Field('Lastname', 'string'),Field('Age', 'integer'))
I've also tried the following views:
{{=response._vars[next(iter(response._vars))]}}
RESULT (first name still cut off):
<table><thead><tr><th>example_data.id</th><th>example_data.Firstname</th><th>example_data.Lastname</th><th>example_data.Age</th></tr></thead><tbody><tr class="w2p_odd odd"><td>1</td><td>Jill</td><td>Smith</td><td>1</td></tr><tr class="w2p_even even"><td>2</td><td>Eve</td><td>Jackson</td><td>33</td></tr><tr class="w2p_odd odd"><td>3</td><td>afdaskfdlasjf...</td><td>Jackson</td><td>33</td></tr><tr class="w2p_even even"><td>4</td><td>SUUUUPPPEEEER...</td><td>Jackson</td><td>33</td></tr></tbody></table>
I've also tried the following views:
{{=XML(response._vars[next(iter(response._vars))])}}
RESULT (Lost all HTML formatting):
example_data.id,example_data.Firstname,example_data.Lastname,example_data.Age
1,Jill,Smith,1
2,Eve,Jackson,33
3,afdaskfdlasjfkdlsjfklajdfskasjfklsdajfdklsajfklsajfdskalfdjsakldfjklasfjkdlsajfdsakljdklsadcjklasjcklsjackldsjakfldajsfklasdfjklasjfdklajfdsklsjafkldasjfkldasjkldsjcklajsckljackldajsdfklfjkalsncklacnkalsdfjkldasnckldasjckljsdaklfdnfkldsajfdklasjldsk,Jackson,33
4,SUUUUPPPEEEERRRR LONGGGGG FIRSTTTT NAMEEEE,Jackson,33
All of those methods ultimately result in the DAL Rows object being passed to SQLTABLE for rendering in the view. By default, fields are truncated to 16 characters. If you want to change that, you will have to call SQLTABLE explicitly:
{{=SQLTABLE(content, truncate=100)}}
Good day, I have the following problem that I'm trying to resolve. What I need to achieve is having the patient id generated as i run the app and print it. But it is not doing what I want. I want each time that I will run the app, and calling the view, a new id as to get generated and printed for now.
Here's a class that I have created to generate the id:
import uuid
class PatientId:
def __init__(self, id_number):
self.id_number = id_number
#staticmethod
def generate_patient_id_number():
prefix = 'HSCM'
generated_id = str(uuid.uuid4().hex[:6].upper())
return '%s-%s' % (prefix, generated_id)
and in my views.py,
from utilities.id_number import PatientId
# Create your views here.
def show_id(request, self):
id = PatientId(self).generate_patient_id_number()
print(id)
return render(request, 'index.html', {})
Will appreciate any help
Not sure why you have PatientId as a class, but with that given class you can use it like this, since generate_patient_id_number() is a static method
id = PatientId.generate_patient_id_number()
# id is e.g. 'HSCM-E9E10C'
You don't need a class for this
In Python you don't need to make everything into a class, so if you had a module called id_number you can simply put that function there (and then you can use it from anywhere, a view, a class, a Django model, etc)
# id_number.py
def generate_patient_id_number():
prefix = 'HSCM'
generated_id = str(uuid.uuid4().hex[:6].upper())
return '%s-%s' % (prefix, generated_id)
and use it like this
from utilities import id_number
def show_id(request):
id = id_number.generate_patient_id_number()
print(id)
return render(request, 'index.html', {})
I am trying to get a 'GET' parameter from one of my template pages, called 'id' (ex. issues.html?id=1), and using it to perform a variety of retrievals from my sqlite database. However, when I run the following code, I get the error "'QueryDict' object has no attribute 'id'"
Here is some of my code in views.py:
def issues(request):
# Retrive basic legislation details from GET parameter
legislation_id = request.GET.id
legislation = Legislation.objects.filter(id = legislation_id)[0]
# Getter functions from database - names are pretty self-explanatory
def get_buttons():
button_html = ""
for tag in legislation.demographic_tags:
button_html += "<input type='button' class='button' onclick='showtag"+tag.hash_code+"();' value='"+tag.name+"'>"
return button_html
def get_description():
return legislation.description
def get_script():
return ""
def get_sponsor():
return legislation.sponsor
def get_summary():
return legislation.summary
def get_title():
return legislation.name
# Return results back to page
return render(request, 'issues.html',
{'buttons': get_buttons(), 'description': get_description(), 'script': get_script(), 'sponsor': get_sponsor(),
'summary': get_summary(), 'title': get_title()})
Your problem seems to be here: legislation_id = request.GET.id
request.GET is a dictionary so you need to get its key like this:
legislation_id = request.GET["id"]
You can also get the key with dict's get method as #Alvaro suggested: request.GET.get("id", 0). This will return 0 as default if id key ain't present and save you from KeyError exception.
Hope this helps!
I want to create a new type of field for django models that is basically a ListOfStrings. So in your model code you would have the following:
models.py:
from django.db import models
class ListOfStringsField(???):
???
class myDjangoModelClass():
myName = models.CharField(max_length=64)
myFriends = ListOfStringsField() #
other.py:
myclass = myDjangoModelClass()
myclass.myName = "bob"
myclass.myFriends = ["me", "myself", "and I"]
myclass.save()
id = myclass.id
loadedmyclass = myDjangoModelClass.objects.filter(id__exact=id)
myFriendsList = loadedclass.myFriends
# myFriendsList is a list and should equal ["me", "myself", "and I"]
How would you go about writing this field type, with the following stipulations?
We don't want to do create a field which just crams all the strings together and separates them with a token in one field like this. It is a good solution in some cases, but we want to keep the string data normalized so tools other than django can query the data.
The field should automatically create any secondary tables needed to store the string data.
The secondary table should ideally have only one copy of each unique string. This is optional, but would be nice to have.
Looking in the Django code it looks like I would want to do something similar to what ForeignKey is doing, but the documentation is sparse.
This leads to the following questions:
Can this be done?
Has it been done (and if so where)?
Is there any documentation on Django about how to extend and override their model classes, specifically their relationship classes? I have not seen a lot of documentation on that aspect of their code, but there is this.
This is comes from this question.
There's some very good documentation on creating custom fields here.
However, I think you're overthinking this. It sounds like you actually just want a standard foreign key, but with the additional ability to retrieve all the elements as a single list. So the easiest thing would be to just use a ForeignKey, and define a get_myfield_as_list method on the model:
class Friends(model.Model):
name = models.CharField(max_length=100)
my_items = models.ForeignKey(MyModel)
class MyModel(models.Model):
...
def get_my_friends_as_list(self):
return ', '.join(self.friends_set.values_list('name', flat=True))
Now calling get_my_friends_as_list() on an instance of MyModel will return you a list of strings, as required.
What you have described sounds to me really similar to the tags.
So, why not using django tagging?
It works like a charm, you can install it independently from your application and its API is quite easy to use.
I also think you're going about this the wrong way. Trying to make a Django field create an ancillary database table is almost certainly the wrong approach. It would be very difficult to do, and would likely confuse third party developers if you are trying to make your solution generally useful.
If you're trying to store a denormalized blob of data in a single column, I'd take an approach similar to the one you linked to, serializing the Python data structure and storing it in a TextField. If you want tools other than Django to be able to operate on the data then you can serialize to JSON (or some other format that has wide language support):
from django.db import models
from django.utils import simplejson
class JSONDataField(models.TextField):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if value is None:
return None
if not isinstance(value, basestring):
return value
return simplejson.loads(value)
def get_db_prep_save(self, value):
if value is None:
return None
return simplejson.dumps(value)
If you just want a django Manager-like descriptor that lets you operate on a list of strings associated with a model then you can manually create a join table and use a descriptor to manage the relationship. It's not exactly what you need, but this code should get you started.
Thanks for all those that answered. Even if I didn't use your answer directly the examples and links got me going in the right direction.
I am not sure if this is production ready, but it appears to be working in all my tests so far.
class ListValueDescriptor(object):
def __init__(self, lvd_parent, lvd_model_name, lvd_value_type, lvd_unique, **kwargs):
"""
This descriptor object acts like a django field, but it will accept
a list of values, instead a single value.
For example:
# define our model
class Person(models.Model):
name = models.CharField(max_length=120)
friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120)
# Later in the code we can do this
p = Person("John")
p.save() # we have to have an id
p.friends = ["Jerry", "Jimmy", "Jamail"]
...
p = Person.objects.get(name="John")
friends = p.friends
# and now friends is a list.
lvd_parent - The name of our parent class
lvd_model_name - The name of our new model
lvd_value_type - The value type of the value in our new model
This has to be the name of one of the valid django
model field types such as 'CharField', 'FloatField',
or a valid custom field name.
lvd_unique - Set this to true if you want the values in the list to
be unique in the table they are stored in. For
example if you are storing a list of strings and
the strings are always "foo", "bar", and "baz", your
data table would only have those three strings listed in
it in the database.
kwargs - These are passed to the value field.
"""
self.related_set_name = lvd_model_name.lower() + "_set"
self.model_name = lvd_model_name
self.parent = lvd_parent
self.unique = lvd_unique
# only set this to true if they have not already set it.
# this helps speed up the searchs when unique is true.
kwargs['db_index'] = kwargs.get('db_index', True)
filter = ["lvd_parent", "lvd_model_name", "lvd_value_type", "lvd_unique"]
evalStr = """class %s (models.Model):\n""" % (self.model_name)
evalStr += """ value = models.%s(""" % (lvd_value_type)
evalStr += self._params_from_kwargs(filter, **kwargs)
evalStr += ")\n"
if self.unique:
evalStr += """ parent = models.ManyToManyField('%s')\n""" % (self.parent)
else:
evalStr += """ parent = models.ForeignKey('%s')\n""" % (self.parent)
evalStr += "\n"
evalStr += """self.innerClass = %s\n""" % (self.model_name)
print evalStr
exec (evalStr) # build the inner class
def __get__(self, instance, owner):
value_set = instance.__getattribute__(self.related_set_name)
l = []
for x in value_set.all():
l.append(x.value)
return l
def __set__(self, instance, values):
value_set = instance.__getattribute__(self.related_set_name)
for x in values:
value_set.add(self._get_or_create_value(x))
def __delete__(self, instance):
pass # I should probably try and do something here.
def _get_or_create_value(self, x):
if self.unique:
# Try and find an existing value
try:
return self.innerClass.objects.get(value=x)
except django.core.exceptions.ObjectDoesNotExist:
pass
v = self.innerClass(value=x)
v.save() # we have to save to create the id.
return v
def _params_from_kwargs(self, filter, **kwargs):
"""Given a dictionary of arguments, build a string which
represents it as a parameter list, and filter out any
keywords in filter."""
params = ""
for key in kwargs:
if key not in filter:
value = kwargs[key]
params += "%s=%s, " % (key, value.__repr__())
return params[:-2] # chop off the last ', '
class Person(models.Model):
name = models.CharField(max_length=120)
friends = ListValueDescriptor("Person", "Friend", "CharField", True, max_length=120)
Ultimately I think this would still be better if it were pushed deeper into the django code and worked more like the ManyToManyField or the ForeignKey.
I think what you want is a custom model field.