## Details ##
Type: Drupal module for 6.x
Projectname: crud (already exists, discussion below)
## About "crud" ##
Crud is a module that autogenerates admin forms from the database schema provided in the .install files. Extra information needed is set from a hook_crud(), which keeps the .install files untainted.
Crud currently has:
* one-to-many / many-to-one relationships
* the following field types: 'int', 'varchar', 'text', 'bool', 'weight'
* overwriting of form settings for individual fields
* caching
* support for cascading deletion
* having empty fields by setting 'not null' => TRUE in the .install file
## Motivation ##
I hate writing crud code, and I have created crud solutions for Joomla, Prescriba Framework (our own) and now drupal. This is the first time though I've coded support for relationships.
The reason I want to share this now, is that I could really need some eyes on this (naming conventions, bugfinding, code), and some people to discuss with.
I work for a healthcare company called Prescriba (www.prescriba.com), I have developed crud for them, and can partly support it from work as well.
## Naming ##
I see that there is allready a module called crud but that is merely some database functions. I dont care about the name, so let me know what you think.
## Duplication / crud module & Drupal overall strategy ##
I know there is a lot that could be said here, especially in regards to CCK, the new db_api comming in drupal 7, but I really don't have any answers. What do you think?
## State of the code ##
Its working fine and we have no known bugs with it, but I am sure there is many edge cases, that will show up if more people begin using it. There is also room for a _lot_ of improvement with the code in general. Also it would be nice to support many-to-many relationship properly. I would call it Alpha quality.
## Todo ##
* settle on naming conventions
* document all features
* 100% follow drupals coding conventions. ATM this is clashing a bit with that of my company. Looking for a way to auto convert back and forth.
## How it works ##
See code in crud_example module.
## Download ##
Consists of two modules:
* crud - the crud module
* crud_example module - a test module. Crud forms is accessed at http://[URL]/admin/labels
Download here: http://rune.prescriba.com/public/crud.tar.gz
| Comment | File | Size | Author |
|---|---|---|---|
| #60 | autoadmin.tar_.gz | 13.26 KB | _rune |
| #44 | autoadmin.tar_.gz | 17.78 KB | _rune |
| #38 | autoadmin.tar_.gz | 17.83 KB | _rune |
| #36 | autoadmin.tar_.gz | 16.66 KB | _rune |
| #34 | autoadmin_translation.png | 63.77 KB | _rune |
Comments
Comment #1
_rune commentedComment #2
avpadernoComment #3
_rune commentedHi KiamLaLuno
Thx for your reply. My comments to that:
1) I am familiar with the "coding standards" and my current style is not very far from it. Last I ran the code through the coder module, It gave only minor errors. Should it be accepted as Drupal project, I will change it to 100% compliance with Drupal coding style. There is several thousands lines of code, and it will take a _long_ time to make it pass the coder module with 0 errors.
2) To quote from the current crud project:
"This is a utility / support module that simplifies SELECT, INSERT, UPDATE and DELETE operations for tables with a simple primary key (either an auto_increment key or a key defined in the sequences table)"
My module automatically generates:
* A list page of all items in the table (including pagination and ordering)
* An edit form for a single item
* A deletion form for a single item (including support for cascading deletion)
* Support for one-to-many / many-to-one and one-to-one table relationships
Only based on the schema in the .install file and a bit of additional configuration. So I _really_ don't think the two modules have the same purpose. But again I have no problems changing the name.
3) To automatically create forms based on database schemas is not currently a part of Drupal and can not easily be implemented using Drupal core functions. At least not without writing 80+ lines of code for each table you want implemented. Using crud module, it takes about 5 lines of configuration data for a table with no relationships.
Did you read through the code / try to install the modules?
Thanks for your time!
Comment #4
_rune commentedAdded a screenshot of what the code looks like and the result you get.
Comment #5
_rune commentedSorry for spamming. Here is a pdf file of the list,edit and delete view.
Comment #6
_rune commentedComment #7
DougKress commentedAs you have stated, your module does a lot more than create, read, update & delete operations. I would recommend changing the name to something with the word "form" in it -- db_form, auto_form, form_builder.
The goals of the existing crud module is to simplify database operations. I've updated the description for the existing crud module (http://drupal.org/project/crud) with an example of the main crud_save function.
Comment #8
_rune commentedThx a lot for quick reply.
To me Crud operations has always have something to do with forms, but I can see that you can consider CRUD both at the database level and at the User Interface level.
Will recommit this project under another name.
Comment #9
_rune commentedComment #10
avpadernoI am not sure it is possible to open another CVS application when one is already opened.
If you want really to not continue with this application, you should tell us.
Comment #11
_rune commentedAhh ok sure. Will refactor my code into a new module name, and upload it to this thread. If just I could figure out a good name :)
Edit: Am thinking about "Auto Admin"
Thx!
Comment #12
avpadernoCRUD is always associated with database operations, not with forms.
db_escape_string().Comment #13
_rune commentedThx for feedback!
1) No you are right. I've already fixed the naming of the functions in crud.inflector.inc and db.inc (now crud.db.inc). In my own setup I have a inflector and a db module, so just copied the files from there and forgot to change the function names. Also fixed all errors when running it through the coder module. These changes will appear in my next upload.
2) Was not aware that backticks are MySQL specific, thanks for pointing that out.
3) I see what you mean, will change it into using placeholders instead of doing the MySQL by hand.
Still thinking about that name... will upload a new and improved version when I think of one.
Comment #14
_rune commentedSo here is a new version called: Auto Admin, folder name: "autoadmin".
## Changes ##
* Removed backticks. Might put them back in when mysql or mysqli driver is in use.
* Fixed naming of functions / files.
* Verified code with hardest setting in coder module.
## Not changed: Placeholders ##
I looked into how Drupal does it, and it does something similar to what I am doing: http://api.drupal.org/api/function/_db_query_callback/6, so my solution should not be more unsafe, although less optimized. My main problem is that I really like having the autoadmin_db_where() function that returns a string like "foo='bar' AND foz='baz'" from an array, and it gets more complicated (but certainly doable) to return an array like array("foo='%s' AND foz='%s'", array('bar', 'baz')). But if you really want, I will change it!
All the best
Rune
Comment #15
avpadernoThe difference is that
_db_query_callback()is a low level function that is never directly called from a module. It's quite obvious that such function exists, otherwise the queries could not be executed.Modules should use placeholders, rather than building an SQL query by concatenating strings; being contained in a module, your code doesn't make an exception.
EDIT: There are two exception to the use of placeholders: when using
update_sql()(which is called by update functions, and which doesn't allow the use of placeholders), and when the string is a constant (which means a string not obtained by any function).Comment #16
_rune commentedHi
Here is an upload of autoadmin.db.inc with placeholders instead of manually escaping the sql. If it is in accordance with drupal guidelines (?) I will continue with the other files, but that is going to take some more work.
Cheers
Comment #17
avpadernoThese functions do something already done by a Drupal function.
Comment #18
avpadernoComment #19
_rune commented## autoadmin_db_get_placeholder ##
If you are referring to http://api.drupal.org/api/function/db_type_placeholder/6 this is based on the type of field in the schema, whereas mine is based on the type of the variable. I _should_ use the schema information more in my database functions, but I would like to do that in a later version.
## autoadmin_db_result_has_rows ##
The closest I know of is http://api.drupal.org/api/function/db_result/6, but that moves the pointer of database object. The only other alternative I could find was to first do a "COUNT(*)" mysql call, but that is not so efficient.
Comment #20
avpadernoI was thinking of db_affected_rows(), which can be used after an INSERT, UPDATE, REPLACE or DELETE query; when the previous query is a SELECT, it's more probable that
db_result(db_query("SELECT COUNT(*) FROM {table} WHERE // ..."))is faster than the code of your function.Comment #21
_rune commentedYir, you were right about the placeholders, even though it was a bit of a chore to change, it feels a lot nicer! Thx.
EDIT: Also helped med catch a couple of places where I forgot to escape variables before adding them to the sql.
EDIT2: Meaning that now I don't need to, because db_query() does it for me.
## Changes ##
* all db_query() calls uses placeholders
* removed the autoadmin_db_result_has_rows() function
* minor cleanups
Comment #22
_rune commentedHere is a new version.
## Changes ##
* No one line if sentences
* Non-documentation comments now are capitalized sentences with punctuation
* added @ingroup for form and @file docblocks
* added @see for form docblocks
* named parameters now have space around " = "
Comment #23
_rune commentedHi again
Have not heard from anybody on this for a while, of reasons which I do not now, but I got a new version ready:
## Changes ##
* Removed all my custom database functions, everything is using db_query(), pager_query(), db_fetch_array() and drupal_write_record() now.
* Revamped code for SQL query generation.
* SQL generated is a lot more human readable
* The code is a lot tighter and easier to understand.
* Schemas with errors are not added to menu.
* Less magic more Drupal.
EDIT: The information about what this module actually does(Create administration forms automatically) is a bit scattered in this thread. Let me know if you need a rewrite of it!
EDIT2: To the best of my knowledge this version meets the changes suggested by KiamLaLuno.
EDIT3: KiamLaLuno has handled this process until now, but if he is too busy, maybe I could convince someone else to take a look? Sorry for nagging! :)
Comment #24
_rune commentedNew version:
## Changes ##
* Added functional tests.
Comment #25
avpadernoThe title used for the field is not translated, and the description will never be translated because the script used to extract the strings to translate works only when the argument passed to
t()is a constant string (not even a concatenation of strings).The code is making assumptions about the size of the field.
Comment #26
avpadernoI am not sure this would work; the string that replaces the placeholder
%sis passed todb_escape_string(), which is not used to escape a field name.As reported in the previous comment, a function which returns the form field definition array doesn't allow to use translatable strings; just for this fact, I think it will be difficult to persuade other developers to use your module.
Comment #27
_rune commentedHi
Thx a lot for feedback! Will address the issues mentioned. I could use some clarification regarding #26.
This works
But I agree that it is undrupalish. Would you prefer
or
?
Cheers
Comment #28
avpadernoOf the two choices, it's preferable the first one. Still, the SQL query you are building could suffer of SQL injection (especially if the module using yours is accepting inpout from a user).
Comment #29
_rune commentedYes. All these three are taken from the schema supplied by the developer in hook_autoadmin(). Will change all places of using placeholders for SELECT and FROM statements into string concatenation, while keeping SQL injection in mind.
Comment #30
_rune commentedNew version
## 25 ##
1. Now it is, or I just dont see it.
2. Everything in the userinterface is translateable now.
3. The size of the field was MySQL specific (http://help.scibit.com/Mascon/masconMySQL_Field_Types.html). Now returns default drupal values for other database engines.
## 26 ##
Fixed
## Other changes ##
* Cleanups and refactorings.
Comment #31
_rune commentedComment #32
_rune commentedFixed a bug (and added tests) regarding pagination. Please review this version instead.
Comment #33
avpadernoThe code should use
module_load_include().Title and description will not be translated, as the argument passed to
t()is not a literal string.The message passed to
drupal_set_message()should use placeholders, and not concatenated strings (which cause the script that extracts the strings to translate to report an error); in the case$schema, or$fieldare empty, the message is simply a concatenation of two empty strings (what should a user understand, from that?).Comment #34
_rune commentedHey, thx for feedback!
## 1 ##
Fixed, but seems that it must run a lot more code now to do the same job.
## 2 ##
Fixed, see ## 4 ##
## 3 ##
Fixed bug in autoadmin_add_error(), and added the unit tests testAddErrorOneArgument(), testAddErrorTwoArguments(), testAddErrorThreeArguments() to test that it works.
## 4 ##
Think I finally understand the full extent of the problem now. Before I did not consider the need to make export/extract of .pot files work. Here are two different ways to define autoadmin schemas:
1)
Here all text in the schema are using the t() function and can thus be extracted to a pot file.
2)
This is a lot easier to write but extract of .pot files does not work. I plan to make a pot extracter. To translate this the user must manually search for each phrase in the translation interface, or use the Translation Client module (See attached screenshot showing a danish translation).
## Whats else is new ##
* Added languages folders in both autoadmin and autoadmin_example with extracted pot files.
* Cleanups and refactorings.
Thanks a lot!
Rune
Comment #35
avpadernoThe function implemented by the proposed module are thought to be used from another module, not by the final user of a Drupal-powered site.
A module developer creating a module depending on the proposed one would find that his module uses some not translatable strings; he could write the same code present in the proposed module (or equivalent functions), and he would have translatable strings (to say to a user of a module that he needs another module to translate a module strings when this is not required from other modules is not something that would make the user happy).
The solution to the problem is to avoid the module emits any error messages, which should be a task left to the calling module (which will be free to use the error message it wants, and to have them really translatable without to require the user to install a third-party module).
Comment #36
_rune commentedHi Again
1)
I'm sorry but I don't understand what you mean about the messages. All error messages are translatable and gives hints to the developer when he has made a mistake in the schema definition.
2)
Drupal 6 does not support extraction of non literal strings, so I've figured that instead of creating yet another .pot extractor I could just write the strings as php code to a dummy file. This is not pretty but is the only way to make translation works seamlessly with the .pot extractor tools. See autoadmin_dynamic_t() and autoadmin_schema_write_strings_as_code(). This only runs during flushing of cache, and makes everything work as it should.
New version attached.
Cheers
Rune
Comment #37
avpadernoThe string is a concatenation of strings, when it should use a placeholder.
Also here the string is a concatenation of strings; some values of
$schema, and$fieldwould cause the message to be an empty string (which is not very useful as message).Comment #38
_rune commentedAdded language files for autoadmin. Please review this version instead.
EDIT: disregard, you replied with #37 while I wrote this.
Comment #39
_rune commented1)
Since $foreign_schema['title_plural'] already is a translated string, to use this...
...would add an unnecessary translation burden. You could argue that in some languages it might be more correct with the reverse ordering of words, but I think it is a good trade off.
2)
Message is never empty because the variable $msg is never empty. It is concatenated to the optional [SCHEMA: foo] [FIELD: bar]. But there is always a message.
3)
I have double checked the 84 uses of t() and autoadmin_dynamic_t() and they are all a conscious choice. The places I am not using placeholders are places where it would add an unnecessary translation burden.
Thx for feedback!
Comment #40
avpadernoThere are actually languages where the reversed order of words is not the more correct, but it is the only way to write a sentence.
Languages where the order of the words is OSV don't write first the verb.
Comment #41
_rune commentedLOL...this is cool:)
I'm by no means a linguist, did not even know what OSV meant before two minutes ago. Found this at http://jurisdynamics.blogspot.com/2008/02/themes-and-rhemes-and-xsv-smil....
So the current version does not support "Yoda" mode, as he apparently speaks mostly OSV;)
But I still strongly feel that with the extra amount of translation strings this would give (maybe about 20 for a standard size schema), it is not worth it.
But you are the boss and if you insist I will add something like a 'advanced translation' => TRUE setting, that translates with placeholders. But that seems very much to me to be a case of YAGNI. I would like to release it like it is now, and first change it if it becomes a problem. There are several other features/enhancements that in my eyes would enhance the module more.
Thx!
R
Comment #42
_rune commentedComment #43
avpadernoAs far as I can see, other modules support any kind of languages (whatever they are SVO, OSV, etc...). As there is a request for adding the Central Morocco Tamazight in l.d.o, nobody can know if somebody will ask to add the support for a OSV language (Drupal supports any language).
Comment #44
_rune commentedadded support for Central Morocco Tamazight, and "Yoda Mode" :)
Comment #45
avpadernoMay you explain how autoadmin.dynamic_strings.inc should resolve the problem of the strings not being translated?
Comment #46
_rune commentedGenerating php code in autoadmin.dynamic_strings.inc solves the problem of strings not being extracted with .pot tools.
This is how I solved translation of dynamically generated strings, so it works with drupal core modules:
Please consider the following code:
This is translatable. The user can browse to "admin/build/translate/search", search for "Some text.", translate it and it works fine. However it generates two problems:
1) Errors shown on "admin/build/translate/export" page. This I solved with the autoadmin_dynamic_t() function that wont trigger these errors.
2) "Some text." is not extracted to the .pot file generated at "admin/build/translate/export". This I solved with generating dummy php code and writing it to autoadmin.dynamic_strings.inc. See the autoadmin_schema_write_strings_as_code() function.
Comment #47
avpadernoThe content of the file is the following:
Are those all the dynamic strings the module could get? Considering that the module is thought to be potentially used from many modules, how can that file contain all the possible strings used by the modules?
autoadmin_dynamic_t()is just a trick that doesn't resolve the problem of the extraction script not being able to extract the strings to translate. If the extraction script doesn't extract a string, then the template file doesn't contain that string, and people translating the module will not be able to translate it (as it is not present in the translation template).Comment #48
_rune commented1) + 2) autoadmin.dynamic_strings.inc is regenerated by the autoadmin_schema_write_strings_as_code() function every time the cache is cleared, so it contains _all_ the strings from _all_ modules implementing autoadmin.
Comment #49
avpadernoModules don't normally have write access to the directory where they are installed; that is the reason modules write their data in the directory files set by the Drupal installation they run on.
If I am wrong, let me know where it is stated differently.
Comment #50
_rune commentedAbout using dummy file
Hey found this here: http://api.drupal.org/api/function/t
About the module having write access to its own folder
I checked out where t() functions are extracted from, and they have to be in one of the following folders:
I have neither found nor searched of precedence for a module writing to its own folder, but it is the only option to get the translation to work! Please bear in mind that if the developer is not happy with this he can simply add:
And no writing to disk will occur!!!
I think I've exhausted all the options on this, and landed on the best and most transparent solution.
Cheers
Rune
Comment #51
_rune commentedComment #52
avpadernoWhat reported about a dummy file is true for strings that are not dynamically generated. To make an example, a dummy file can be used in the case the module uses third-party code, and the module knows which strings are returned from that code (it could be, in example, a string for the week day).
What your code does is to:
Am I missing anything?
Comment #53
_rune commentedNo, you are completely right. All strings from all the modules are written to sites/all/modules/autoadmin/autoadmin.potx.inc. So one file needs to be writable!
Writing to autoadmin.potx.inc can be disabled with the 'translate' => FALSE setting, and you have to define your module as the example in (#34.4.1).
The processing and pluralization of database field names to strings makes the job much easier for the developer! If I have a database field called "company" it is a fair assumption that I want the label in the form to be called "Company", and want to use the pluralized string "companies" other places. If I can't write to autoadmin.potx.inc I cant supply this feature. If you have another solution to this catch-22 i'd love to hear it :)
Cheers
Rune
Comment #54
_rune commentedComment #55
_rune commentedI occurred to me that CCK must face the same problem with translating field names inputted by the user. So I decided to check out how CCK handles the problems and it doesnt really. CCK is floating with code like:
So this code is bound to trigger warnings when extracting pot files.
Drupal is not really build for doing this kind of thing, and at least some kind of workaround must be applied.
Comment #56
_rune commentedDid also check out the Views module. Here is what I found:
Comment #57
avpadernoTo ask to the modules the strings to translate is the right way to proceed; as the module is doing that, why doesn't it ask for the translated strings? In that way the single module could call
t()passing to the function the literal string to translate.There is not any problem if you use an already translated string in a call to
t(); the important is to use a placeholder for that string (something liket('Error: %error')).Comment #58
_rune commentedAsking the modules for all the strings to translate allready works! It is just a LOT more annoying to write.
Sorry for repeating, but the difference is shown in the two examples below. In the first one _all_ strings are written. In the second one, autoadmin will decide what the strings should be. This decisionmaking is based on the names of the database names and database fields. The result is exactly the same.
1)
2)
The only drawback is that autoadmin needs to be able to write to one single file. I'm all for purity of code but I also like nice features and ease of use. In this case I think it is ok to write to the file.
NB: I have found several other modules already in Drupal that are writing to their own module directories. If you want I can give you the specifics.
Thx
Rune
Comment #59
avpadernoAs you reported in the previous commit, the code asks to third-party modules the strings to translate, and then it writes them in a file contained in the module directory. Don't you see anything strange in the workflow? The modules could return the translated strings already, without the need to write them in a file.
Then, consider this (which is the main reason the code doesn't work). Users who want to translate the strings in your module downloads the archive, and run the module that extracts the strings to translate; as they didn't run the module (and to create the translation template is not necessary to execute the module first), that file doesn't contain any extra string (except the ones you already added).
Consider also how localize.drupal.org works; it doesn't execute the module to grab all the strings to translate.
Therefore, the proposed way to translate the strings doesn't work.
Comment #60
_rune commentedI agree it would not work on localize.drupal.org, because the strings are collected inside autoadmin. Removed all functionality that relates to dynamic string generation.
Cheers
Rune
Comment #61
avpadernoI still think that using
hook_autoadmin()to get the translated strings from third-party modules was a good idea.Comment #62
_rune commentedI still miss my cool dynamic string generation (might move it to a generator script), but its more drupalish this way and its garantied to work 100% with translation. So I concur.
Its been some ride, thx a lot for all your help and comments!!! Autoadmin wouldn't be as good without them! Sorry if I've been stubborn along the way!
Take care
Rune
Comment #65
avpaderno