changes to support non-primary key in Piccolo Admin#134
changes to support non-primary key in Piccolo Admin#134sinisaos wants to merge 11 commits intopiccolo-orm:masterfrom
Conversation
piccolo_api/crud/endpoints.py
Outdated
| target_pk_type = [ | ||
| i._meta.params["target_column"].value_type not in (int, uuid) | ||
| for i in self.table._meta._foreign_key_references | ||
| ][0] |
There was a problem hiding this comment.
I see the idea. I think the problem though is it's having to do [0]. If the _foreign_key_references contains more than 1 item, we're guessing that the first one is correct.
I think we're going to have to add a GET parameter so the Piccolo Admin UI can explicitly tell us which column it's interested in.
There was a problem hiding this comment.
Yes. That make sense. I will try to add a GET parameter.
There was a problem hiding this comment.
Cool, thanks. I think another advantage of the GET param is if someone doesn't pass it we can return the same response as before, so there are no breaking API changes.
There was a problem hiding this comment.
I didn't get far, but we can pass target_column as a query param
target_column = request.query_params.get("target_column")
if target_column is not None:
return JSONResponse({i["readable"]: i["readable"] for i in values})and return the result like this
# ./api/tables/movie/ids/?target_column=
{
'Star Wars': 'Star Wars',
'Return of the Jedi: 'Return of the Jedi,
...
}
(we don't need to pass anything to query param and return all results because the KeySearchModal already needs all results)
or without the target_column return standard response
# ./api/tables/movie/ids/
{
1: 'Star Wars',
2: 'Return of the Jedi,
...
}
I don't know yet how to trigger Vue frontend based on the existence of the column_name query parameter. I don't know is this ok, or I completely miss the point.
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## master #134 +/- ##
==========================================
+ Coverage 92.55% 92.59% +0.04%
==========================================
Files 33 33
Lines 2027 2039 +12
==========================================
+ Hits 1876 1888 +12
Misses 151 151 ☔ View full report in Codecov by Sentry. |
piccolo_api/crud/endpoints.py
Outdated
| except ValueError: | ||
| return Response("The ID is invalid", status_code=400) | ||
| for i in self.table._meta._foreign_key_references: | ||
| target = i._meta.params["target_column"] |
There was a problem hiding this comment.
This should be i._meta.params.get("target_column") in case the key doesn't exist.
piccolo_api/crud/endpoints.py
Outdated
| reference_target_pk = ( | ||
| await self.table.select(self.table._meta.primary_key) | ||
| .where(target == row_id) | ||
| .first() | ||
| .run() | ||
| ) | ||
| row_id = reference_target_pk[self.table._meta.primary_key] |
There was a problem hiding this comment.
We can do this slightly easier:
reference_target_pk = (
await self.table.select(self.table._meta.primary_key)
.where(target == row_id)
.output(as_list=True)
.first()
.run()
)[0]There was a problem hiding this comment.
Unfortunately this does not work if we have multiple non-primary keys per single table.
|
@sinisaos I see where you're going with this. I'll have another look tomorrow at the UI side. |
| row_id = self.table._meta.primary_key.value_type(row_id) | ||
| except ValueError: | ||
| return Response("The ID is invalid", status_code=400) | ||
| for i in self.table._meta._foreign_key_references: |
There was a problem hiding this comment.
I think we can move the logic around a bit here.
target = i._meta.params.get("target_column")
if target is None:
try:
row_id = self.table._meta.primary_key.value_type(row_id)
except ValueError:
return Response("The ID is invalid", status_code=400)
else:
# insert your new logic hereThere was a problem hiding this comment.
Unfortunately this also does not work if we have multiple non-primary keys per single table. e.g if we add director name as non-primary key (or mix primary or non-primary keys) to review reviewer this does not work. we should to leave this as is.
|
@dantownsend Basically we have a working list of rows with only primary keys, only with non-primary keys, or a mixture of both. |
|
@dantownsend After a lot of experimentation I managed to solve the filter search problem to some extent. My approach is to always use a primary key even if table has a non-primary key FK relation to table. Everything works if it only has a non-primary key or a combination of primary key and non-primary key. Also tests for that need to be written. class Review(Table):
reviewer = ForeignKey(Director, target_column=Director.name)
...
movie = ForeignKey(Movie, target_column=Movie.name)because the loop in For referencing table I took same approach and we need to make this changes in Piccolo Admin // changes in ReferencingTables.vue
<script lang="ts">
import Vue from "vue"
import { Location } from "vue-router"
import { TableReferencesAPIResponse, TableReference } from "../interfaces"
export default Vue.extend({
props: ["tableName", "rowID"],
data() {
return {
references: [] as TableReference[],
showReferencing: false
}
},
computed: {
selectedRow() {
return this.$store.state.selectedRow
},
schema() {
return this.$store.state.schema
}
},
methods: {
async fetchTableReferences() {
const response = await this.$store.dispatch(
"fetchTableReferences",
this.tableName
)
this.references = (
response.data as TableReferencesAPIResponse
).references
},
clickedReference(reference: TableReference) {
let columnName = reference.columnName
let query = {}
for (const key in this.selectedRow) {
if (this.rowID == this.selectedRow[key]) {
query[columnName] =
this.selectedRow[this.schema.primary_key_name]
}
}
let location: Location = {
name: "rowListing",
params: { tableName: reference.tableName },
query
}
let vueUrl = this.$router.resolve(location).href
window.open(
`${document.location.origin}${document.location.pathname}${vueUrl}`,
"_blank"
)
}
},
async mounted() {
await this.fetchTableReferences()
}
})
</script>Can you try this changes with reviewer column like this class Review(Table):
reviewer = Varchar()
#and then
class Review(Table):
reviewer = ForeignKey(Director)
# and then
class Review(Table):
reviewer = ForeignKey(Director, target_column=Director.name)What do you think about this? |
|
@sinisaos It looks promising - I'll give it a go. |
|
@dantownsend We also need to make these changes to add and edit records in Piccolo Admin to make everything work. // changes in EditRowForm.vue
methods: {
async submitForm(event) {
console.log("Submitting...")
const form = new FormData(event.target)
const json = {}
const targetColumn = []
for (const i of form.entries()) {
const key = i[0].split(" ").join("_")
let value = i[1]
if (value == "null") {
value = null
} else if (this.schema.properties[key].type == "array") {
// @ts-ignore
value = JSON.parse(value)
} else if (
this.schema?.properties[key].format == "date-time" &&
value == ""
) {
value = null
} else if (
this.schema?.properties[key].extra.foreign_key == true &&
value == ""
) {
value = null
} else if (
this.schema?.properties[key].extra.foreign_key == true &&
this.schema?.properties[key].format != "uuid" &&
this.schema?.properties[key].type != "integer" &&
key == this.schema?.properties[key].extra.to
) {
targetColumn.push(value)
}
json[key] =
targetColumn[1] == value &&
this.schema?.properties[key].format != "uuid" &&
this.schema?.properties[key].type != "integer"
? targetColumn[0]
: value
}
...and // changes in AddRowForm.vue
methods: {
async submitForm(event) {
console.log("I was pressed")
const form = new FormData(event.target)
const json = {}
const targetColumn = []
for (const i of form.entries()) {
const key = i[0].split(" ").join("_")
let value = i[1]
if (value == "null") {
value = null
// @ts-ignore
} else if (this.schema.properties[key].type == "array") {
// @ts-ignore
value = JSON.parse(value)
// @ts-ignore
} else if (
this.schema?.properties[key].format == "date-time" &&
value == ""
) {
value = null
// @ts-ignore
} else if (
this.schema?.properties[key].extra.foreign_key == true &&
value == ""
) {
value = null
} else if (
this.schema?.properties[key].extra.foreign_key == true &&
this.schema?.properties[key].format != "uuid" &&
this.schema?.properties[key].type != "integer" &&
key == this.schema?.properties[key].extra.to
) {
targetColumn.push(value)
}
json[key] =
targetColumn[1] == value &&
this.schema?.properties[key].format != "uuid" &&
this.schema?.properties[key].type != "integer"
? targetColumn[0]
: value
}
... |
|
@dantownsend Did you have time to try this? If you agree with this, I can do PR in Piccolo Admin, with changes from the comments to make everything work. |
|
@sinisaos I haven't had a chance, but I would like to get it sorted. If you could create the PR that would be great, thanks. |
|
@dantownsend Great. I will wait with PR because these changes in Piccolo Admin are only relevant if the code from this PR is good enough. |
|
@sinisaos OK, makes sense |
|
@dantownsend Everything should work now. Combination of primary and non-primary keys per table, multiple non-primary keys per table etc. |
|
@dantownsend Have you had a chance to try this? |
|
@sinisaos Not yet - will try and review it this weekend. |
|
@dantownsend Great. Thanks. |
Related to Supporting foreign keys which reference non-primary keys #144