-
Notifications
You must be signed in to change notification settings - Fork 26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
changes to support non-primary key in Piccolo Admin #134
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. That make sense. I will try to add a GET parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 Report
❗ Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please install the Github App Integration for your organization. Read more. @@ 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
|
piccolo_api/crud/endpoints.py
Outdated
@@ -871,7 +889,16 @@ async def detail(self, request: Request) -> Response: | |||
try: | |||
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: | |||
target = i._meta.params["target_column"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
@@ -871,7 +889,16 @@ async def detail(self, request: Request) -> Response: | |||
try: | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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