I am creating a web store in SilverStripe from scratch without using a shop module. I am having trouble creating the shopping cart.
If I were to make a web store without any CMS system I would create 3 tables. A User table, a Product table and an Order table.
In the Order table there would be stored an ID for the order, a UserID that links to a user, a ProductID that links to a product and a Quantity.
Because I am using SilverStripe I can't directly do that. I can create tables like that but because it isn't the purpose of creating and running queries that is not the way to do it.
How do I do this correctly using SilverStripe?
I am aware of various opensource shopping modules for SilverStripe, but I found them confusing and I have no idea how they link to each other.
You can have SilverStripe create the database tables for you by extending its DataObject and Page classes.
The following code is for SilverStripe 3.1.
Here is how to create the User, Product and Order classes to create the database tables you want, with the described relationships. I've also added in an OrderItem class as I think it makes sense to.
class User extends DataObject {
private static $db = array(
'FirstName' => 'Text',
'LastName' => 'Text',
'Email' => 'Text'
);
}
class Product extends DataObject {
private static $db = array(
'Title' => 'Text',
'Price' => 'Decimal(19,8)'
);
}
class Order extends DataObject {
private static $has_one = array(
'User' => 'User'
);
private static $has_many = array(
'OrderItems' => 'OrderItem'
);
}
class OrderItem extends DataObject {
private static $has_one = array(
'Order' => 'Order',
'Product' => 'Product',
'Quantity' => 'Int'
);
}
Once you have created these classes run dev/build?flush=1 and then have a look at the tables that have been created in your database.
For Product you could extend Page instead of DataObject if you wanted the products to be displayed as pages to the user. This is up to you.
Use SilverStripe to manage your classes, relationships and the database. That's what it is there for.
If you want an excellent shop module in SilverStripe I recommend checking out SwipeStripe. Or if you do want to build this yourself you can check out SwipeStripe's source code on git to see how they do things.
Related
Assuming I have a Django model with a number of relationships that are related, is it possible to nest them through a non-model type for querying purposes? A specific example:
Assume I have a model Organization with relationships that include X_projects, X_accounts, etc which are also Django models.
It is pretty easy to allow queries like:
query fetchOrganization($id: Int!) {
organization(id: $id) {
id,
... other fields ...
X_accounts {
...
}
X_projects {
...
}
}
}
but I would prefer to support queries like:
query fetchOrganization($id: Int!) {
organization(id: $id) {
id,
... other fields ...
X {
accounts {
...
}
projects {
...
}
}
}
}
Given that X doesn't actually make sense to be a Django model / relation on the backend, is there a way to achieve this?
Yes, you can do this by modifying a new resolver for "X" that uses a custom object type that has fields for accounts and projects.
You'll need to create a new composite object type that is a container for accounts and projects, for example (assuming that you've also defined a DjangoObjectType class already for your account and project models)
class XType(graphene.ObjectType):
account = graphene.Field(AccountType)
project = graphene.Field(ProjectType)
Then modify your Organization type definition to add the new field, something like
class OrganizationType(DjangoObjectType):
x = graphene.Field(XType)
class Meta:
model = Organization
# You might want to exclude the x_project and x_account fields
def resolve_x(self, info, **kwargs):
# You'll have to work out how to parse arguments and fetch account and project
return XType(account=account, project=project)
I'm starting with Ember, and I wanted to know if its possible to do this.
My server model of a book:
Book = {
name: 'string',
author_id: 'number'
}
But in my Ember side, I wanted to have something like this:
Book = {
name: DS.attr('string'),
author: DS.belongsTo('author' , {via: 'author_id'})
}
Is this possible?
Yes, that's possible. You don't define that on the relationship though, you implement transformation behavior in your serializer. So rather than telling Ember that your server calls that relationship something different, you just convert the relationship to the format Ember wants before it's loaded into the store.
For instance, if you're using the RESTSerializer, you can override the keyForRelationship hook.
App.BookSerializer = DS.RESTSerializer.extend({
keyForRelationship: function(key) {
if (key === 'author') {
return 'author_id';
} else {
return key;
}
}
});
This will tell the serializer to get the data for the author relationship from the author_id field in your JSON. It'll also ensure that when it sends JSON back to your server, it converts the author relationship back to the author_id property when serializing.
If you're not using the RESTSerializer, you can probably find the serializer you're using on the Ember Data API documentation page and your serializer will mostly likely have the same method or a very similar method.
Let's say I have a Post object that can contain Images, Videos, and other media types. I can use a GenericForeignKey to link them together. Something like:
class Post(models.Model):
title = models.CharField(...)
text = models.TextField(...)
class AudioMedia(models.Model):
...
class VideoMedia(models.Model):
...
class ImageMedia(models.Model):
...
class MediaObject(models.Model):
post = models.ForeignKey(Post)
order = models.IntegerField()
content_type_media = models.ForeignKey(
ContentType, limit_choices_to={
'model__in': (
'audiomedia',
'imagemedia',
'videomedia')
})
object_id_media = models.PositiveIntegerField()
obj = generic.GenericForeignKey('content_type_media', 'object_id_media')
Now I can easily create an admin interface, like:
class MediaObjectAdminInLine(admin.StackedInline):
model = MediaObject
ct_field = "content_type_media"
ct_fk_field = "object_id_media"
extra = 0
class PostAdmin(admin.ModelAdmin):
inlines = [MediaObjectAdminInLine]
Now the question :) In admin/, I can easily create a new Post. To the post, I can easily add more MediaObject. In the panel, I have a drop down menu to chose the type (audio, video, ...), but I have to manually enter the ID of the object I want to link with Post.
I have tried various extensions, including grappelli. Some provide the ability to lookup the ID of objects to link here. I want the ability to add objects here, eg, add an AudioMedia, a VideoMedia, an ImageMedia, depending on what I pick from the dropdown.
Any suggestions?
You'd need to quite a bit of work to get this going.
You're asking that the admin dynamically display a modelform, based on what model type you chose from a drop down.
Django's admin does not do that (nor do any known extensions to it).
To make this work, you'll have to:
Write a custom JavaScript event handler which captures the onchange of the model select drop down.
Then calls Django's admin and requests the inline modelform for that model.
Updates the current HTML page with that model form.
Then you'll need to intercept the parent model's modelform's save() method to figure out which child modelform it's dealing with, and correctly save it to the database.
Then you'll need to sort out how to get the parent model's modelform to correctly display the appropriate child model's modelform dependent on the model of the child.
Sound daunting? It is.
Here's an easier way:
Just have a single "Media" model. You'll have a few fields on the model that are only valid for one of your types (though there's plenty of crossover).
Name any fields that are specific to a single Media type with a prefix for that mediatype, i.e. image_size', orvideo_title`.
Attach a JavaScript handler to your ModelAdmin which selectively shows and hides fields based on a dropdown for the media type. Something like this:
class MediaAdmin(admin.ModelAdmin):
class Meta:
js = ["js/media-types.js",]
// media-type.js
(function($) {
$(document).ready(function(){
$('.module[id^=module] .row').hide();
$('.module[id^=module] .row.module').show();
$('.module[id^=module] .row.module select').each(function(){
if ($(this).val() != '')
{
var group = $(this).parent().parent().parent().parent();
var field = $(this).parent().parent().parent();
var mtype = $(this).val().toLowerCase();
if (mtype != '')
{
$('.row', group).not(field).slideUp('fast');
$('.row[class*="'+mtype+'"]', group).slideDown('fast');
$('.row[class*="all"]', group).slideDown('fast');
}
else
{
$('.row', group).not(field).slideUp('fast');
}
}
});
$('.module[id^=module] .row.module select').change(function(){
var group = $(this).parent().parent().parent().parent();
var field = $(this).parent().parent().parent();
var mtype = $(this).val().toLowerCase();
if (mtype != '')
{
$('.row', group).not(field).slideUp('fast');
$('.row[class*="'+mtype+'"]', group).slideDown('fast');
$('.row[class*="all"]', group).slideDown('fast');
}
else
{
$('.row', group).not(field).slideUp('fast');
}
});
});
})(django.jQuery);
django-admin-genericfk doesn't work with Django 1.9.
Other than that I only found the following module:
https://github.com/lexich/genericrelationview
which looks well maintained. Unfortunately, its JS code does not work well with how Django CMS sets up jQuery (noConflict jQuery), so it seems that it is not an option for me. But it should be fine if not used in Django CMS pages but the regular Django Admin.
I realize this is pretty old, but this is still the first result when searching for this.
django-admin-genericfk does exactly what you need.
I would like to have model binding relationships loaded automatically when referenced in a template. For example, if I have models like this:
App.User = DS.Model.extend
name: DS.attr 'name'
App.Contact = DS.Model.extend
addedBy: DS.belongsTo 'App.User'
and a view like this:
<div>{{contact.addedBy.name}}</div>
it would be really nice if ember-data caught on that it needs to load the User with a primary key in "addedBy". Currently I have to load the User manually with App.User.find(contact.get('addedBy')) and then the template binding updates to display the user's name.
This is a very simple example but in practice I sometimes find myself traversing relationships pretty far. Is there an easy way to automate this?
Thanks folks!
What about side-loading the associated users when serving contacts?
Assuming you are using Rails & active_model_serializers gem, you would have a ContactSerializer like this:
class ContactSerializer < ActiveModel::Serializer
embed :ids, :include => true
#...
has_one :user
end
Doing so, the user will be auto-populated when contact is retrieved.
See documentation.
Turns out that ember-data does exactly what I want by default, and the problem was a bug in my code.
Make sure that the backend for your adapter's findMany() method returns the records in the same order as the argument array of IDs otherwise your DS.hasMany relationships will act very very weird!
I have the following code which displays all the available main pages that can be used when adding sub pages in my project:
$builder->add('subtocontentid',
'entity',
array(
'class'=>'Shout\AdminBundle\Entity\Content',
'property'=>'title',
'query_builder' => function (EntityRepository $repository)
{
return $repository->createQueryBuilder('s')
->where('s.mainpage = ?1')
->setParameter(1, '1')
->add('orderBy', 's.created ASC');
}
));
In the form, it works alright. It displays the proper title of the mainpage. However, when the form is passed to the database, the page ID is passed to the database. This isn't how I want it to work, I need it to pass on a slug to the database instead.
I understand that the code I'm using retrieves all of the fields in the database. How could I select just the Title field and the Slug field, and then in the form pass the Slug field to the database?
Cheers
You will have to change the Transformer used by EntityType, And it isnt the id being passed to the database it is the entity as the Transformer takes the id and looks up the entity in its list. So in your case it would be an instance of Shout\AdminBundle\Entity\Content